/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-Studio-CLA-applies
*
* MuseScore Studio
* Music Composition & Notation
*
* Copyright (C) 2023 MuseScore Limited
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "systemheaderlayout.h"
#include "systemlayout.h"
#include "tlayout.h"
#include "dom/bracket.h"
#include "dom/factory.h"
#include "dom/part.h"
#include "dom/staff.h"
#include "dom/system.h"
#include "style/defaultstyle.h"
using namespace mu::engraving;
using namespace mu::engraving::rendering::score;
double SystemHeaderLayout::layoutBrackets(System* system, LayoutContext& ctx)
{
size_t nstaves = system->staves().size();
size_t columns = system->getBracketsColumnsCount();
std::vector bracketWidth(columns, 0.0);
std::vector bl;
bl.swap(system->brackets());
for (size_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
for (size_t i = 0; i < columns; ++i) {
for (BracketItem* bi : ctx.dom().brackets(staffIdx)) {
if (bi->column() != i || bi->bracketType() == BracketType::NO_BRACKET) {
continue;
}
Bracket* b = createBracket(system, ctx, bi, i, static_cast(staffIdx), bl, system->firstMeasure());
if (b != nullptr) {
b->mutldata()->bracketHeight.set_value(3.5 * b->spatium() * 2); // dummy
TLayout::layoutBracket(b, b->mutldata(), ctx.conf());
if (bi->bracketType() != BracketType::GROUP) { // Because Group brackets always stay outside
bracketWidth[i] = std::max(bracketWidth[i], b->ldata()->bracketWidth());
}
}
}
}
}
for (Bracket* b : bl) {
delete b;
}
double totalBracketWidth = 0.0;
if (!system->brackets().empty()) {
for (double w : bracketWidth) {
totalBracketWidth += w;
}
}
return totalBracketWidth;
}
Bracket* SystemHeaderLayout::createBracket(System* system, LayoutContext& ctx, BracketItem* bi, size_t column, staff_idx_t staffIdx,
std::vector& bl, Measure* measure)
{
if (!measure) {
return nullptr;
}
size_t nstaves = system->staves().size();
staff_idx_t firstStaff = staffIdx;
staff_idx_t lastStaff = staffIdx + bi->bracketSpan() - 1;
if (lastStaff >= nstaves) {
lastStaff = nstaves - 1;
}
for (; firstStaff <= lastStaff; ++firstStaff) {
if (system->staff(firstStaff)->show()) {
break;
}
}
for (; lastStaff >= firstStaff; --lastStaff) {
if (system->staff(lastStaff)->show()) {
break;
}
}
size_t span = lastStaff - firstStaff + 1;
//
// do not show bracket if it only spans one
// system due to some invisible staves
//
if (span > 1
|| (bi->bracketSpan() == span)
|| (span == 1 && ctx.conf().styleB(Sid::alwaysShowBracketsWhenEmptyStavesAreHidden)
&& bi->bracketType() != BracketType::SQUARE)
|| (span == 1 && ctx.conf().styleB(Sid::alwaysShowSquareBracketsWhenEmptyStavesAreHidden)
&& bi->bracketType() == BracketType::SQUARE)) {
//
// this bracket is visible
//
Bracket* b = 0;
track_idx_t track = staffIdx * VOICES;
for (size_t k = 0; k < bl.size(); ++k) {
if (bl[k]->track() == track && bl[k]->column() == column && bl[k]->bracketType() == bi->bracketType()
&& bl[k]->measure() == measure) {
b = muse::takeAt(bl, k);
break;
}
}
if (b == 0) {
b = Factory::createBracket(ctx.mutDom().dummyParent());
b->setBracketItem(bi);
b->setGenerated(true);
b->setTrack(track);
b->setMeasure(measure);
}
system->add(b);
if (bi->selected()) {
bool needSelect = true;
std::vector brackets = ctx.selection().elements(ElementType::BRACKET);
for (const EngravingItem* element : brackets) {
if (toBracket(element)->bracketItem() == bi) {
needSelect = false;
break;
}
}
if (needSelect) {
ctx.select(b, SelectType::ADD);
}
}
b->setStaffSpan(firstStaff, lastStaff);
return b;
}
return nullptr;
}
void SystemHeaderLayout::addBrackets(System* system, Measure* measure, LayoutContext& ctx)
{
if (system->staves().empty()) { // ignore vbox
return;
}
size_t nstaves = system->staves().size();
//---------------------------------------------------
// find x position of staves
// create brackets
//---------------------------------------------------
size_t columns = system->getBracketsColumnsCount();
std::vector bl;
bl.swap(system->brackets());
for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
for (size_t i = 0; i < columns; ++i) {
for (BracketItem* bi : ctx.dom().brackets(staffIdx)) {
if (bi->column() != i || bi->bracketType() == BracketType::NO_BRACKET) {
continue;
}
createBracket(system, ctx, bi, i, staffIdx, bl, measure);
}
}
if (!system->staff(staffIdx)->show()) {
continue;
}
}
//---------------------------------------------------
// layout brackets
//---------------------------------------------------
layoutBracketsVertical(system, ctx);
setBracketsXPosition(system, measure->x());
muse::join(system->brackets(), bl);
}
double SystemHeaderLayout::totalBracketOffset(LayoutContext& ctx)
{
if (ctx.state().totalBracketsWidth() >= 0) {
return ctx.state().totalBracketsWidth();
}
size_t columns = 0;
for (const Staff* staff : ctx.dom().staves()) {
for (const BracketItem* bi : ctx.dom().brackets(staff->idx())) {
columns = std::max(columns, bi->column() + 1);
}
}
size_t nstaves = ctx.dom().nstaves();
std::vector < double > bracketWidth(nstaves, 0.0);
for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
for (BracketItem* bi : ctx.dom().brackets(staffIdx)) {
if (bi->bracketType() == BracketType::NO_BRACKET || !bi->visible() || bi->bracketType() == BracketType::GROUP) {
continue;
}
//! This logic is partially copied from System::createBracket.
//! Of course, we don't need to worry about invisible staves,
//! but we do need to worry about brackets that span past the
//! last staff.
staff_idx_t firstStaff = staffIdx;
staff_idx_t lastStaff = staffIdx + bi->bracketSpan() - 1;
if (lastStaff >= nstaves) {
lastStaff = nstaves - 1;
}
for (; firstStaff <= lastStaff; ++firstStaff) {
if (ctx.dom().staff(firstStaff)->show()) {
break;
}
}
for (; lastStaff >= firstStaff; --lastStaff) {
if (ctx.dom().staff(lastStaff)->show()) {
break;
}
}
size_t span = lastStaff - firstStaff + 1;
if (span > 1
|| (bi->bracketSpan() == span)
|| (span == 1 && ctx.conf().styleB(Sid::alwaysShowBracketsWhenEmptyStavesAreHidden))) {
Bracket* dummyBr = Factory::createBracket(ctx.mutDom().dummyParent(), /*isAccessibleEnabled=*/ false);
dummyBr->setBracketItem(bi);
dummyBr->setStaffSpan(firstStaff, lastStaff);
dummyBr->mutldata()->bracketHeight.set_value(3.5 * dummyBr->spatium() * 2); // default
TLayout::layoutBracket(dummyBr, dummyBr->mutldata(), ctx.conf());
for (staff_idx_t stfIdx = firstStaff; stfIdx <= lastStaff; ++stfIdx) {
bracketWidth[stfIdx] += dummyBr->ldata()->bracketWidth();
}
delete dummyBr;
}
}
}
double totalBracketsWidth = 0.0;
for (double w : bracketWidth) {
totalBracketsWidth = std::max(totalBracketsWidth, w);
}
ctx.mutState().setTotalBracketsWidth(totalBracketsWidth);
return totalBracketsWidth;
}
void SystemHeaderLayout::setBracketsXPosition(System* system, double xPosition)
{
std::vector brackets = system->brackets();
for (Bracket* b1 : brackets) {
BracketType bracketType = b1->bracketType();
// For brackets that are drawn, we must correct for half line width
double lineWidthCorrection = 0.0;
if (bracketType == BracketType::NORMAL || bracketType == BracketType::LINE) {
lineWidthCorrection = system->style().styleAbsolute(Sid::bracketWidth) / 2;
}
// Compute offset cause by other stacked brackets
double xOffset = 0;
for (const Bracket* b2 : brackets) {
if (!b2->bracketItem()->visible() || b2->bracketType() == BracketType::GROUP) {
continue;
}
if (b1->column() > b2->column() && b1->intersects(b2)) {
xOffset += b2->ldata()->bracketWidth();
}
}
// Set position
double x = xPosition - xOffset - b1->ldata()->bracketWidth() + lineWidthCorrection;
b1->mutldata()->setPosX(x);
}
}
void SystemHeaderLayout::layoutBracketsVertical(System* system, LayoutContext& ctx)
{
for (Bracket* b : system->brackets()) {
int staffIdx1 = static_cast(b->firstStaff());
int staffIdx2 = static_cast(b->lastStaff());
double sy = 0; // assume bracket not visible
double ey = 0;
// if start staff not visible, try next staff
while (staffIdx1 <= staffIdx2 && !system->staves().at(staffIdx1)->show()) {
++staffIdx1;
}
// if end staff not visible, try prev staff
while (staffIdx1 <= staffIdx2 && !system->staves().at(staffIdx2)->show()) {
--staffIdx2;
}
// if the score doesn't have "alwaysShowBracketsWhenEmptyStavesAreHidden" as true,
// the bracket will be shown IF:
// it spans at least 2 visible staves (staffIdx1 < staffIdx2) OR
// it spans just one visible staff (staffIdx1 == staffIdx2) but it is required to do so
// (the second case happens at least when the bracket is initially dropped)
bool notHidden = ctx.conf().styleB(Sid::alwaysShowBracketsWhenEmptyStavesAreHidden)
? (staffIdx1 <= staffIdx2) : (staffIdx1 < staffIdx2) || (b->span() == 1 && staffIdx1 == staffIdx2);
if (notHidden) { // set vert. pos. and height to visible spanned staves
sy = system->staves().at(staffIdx1)->bbox().top();
ey = system->staves().at(staffIdx2)->bbox().bottom();
}
Bracket::LayoutData* bldata = b->mutldata();
bldata->setPosY(sy);
bldata->bracketHeight = ey - sy;
TLayout::layoutBracket(b, bldata, ctx.conf());
}
}
bool SystemHeaderLayout::stackLabelsVertically(System* system)
{
const MStyle& style = system->style();
bool longName = system->ldata()->useLongNames();
InstrumentNamesAlign align
= style.styleV(longName ? Sid::instrumentNamesAlignLong : Sid::instrumentNamesAlignShort).value();
return align == InstrumentNamesAlign::CENTER_CENTER
|| (align == InstrumentNamesAlign::RIGHT_RIGHT && style.styleB(Sid::instrumentNamesStackVertically));
}
void SystemHeaderLayout::computeInstrumentNameOffset(System* system, LayoutContext& ctx)
{
// Get standard instrument name distance
double instrumentNameOffset = ctx.conf().styleAbsolute(Sid::instrumentNameOffset);
// Now scale it depending on the text size (which also may not follow staff scaling)
double textSizeScaling = 1.0;
double actualSize = 0.0;
double defaultSize = 0.0;
bool followStaffSize = true;
if (ctx.state().startWithLongNames()) {
actualSize = ctx.conf().styleD(Sid::longInstrumentFontSize);
defaultSize = DefaultStyle::defaultStyle().value(Sid::longInstrumentFontSize).toDouble();
followStaffSize = ctx.conf().styleB(Sid::longInstrumentFontSpatiumDependent);
} else {
actualSize = ctx.conf().styleD(Sid::shortInstrumentFontSize);
defaultSize = DefaultStyle::defaultStyle().value(Sid::shortInstrumentFontSize).toDouble();
followStaffSize = ctx.conf().styleB(Sid::shortInstrumentFontSpatiumDependent);
}
textSizeScaling = actualSize / defaultSize;
if (!followStaffSize) {
textSizeScaling *= DefaultStyle::defaultStyle().value(Sid::spatium).toDouble() / ctx.conf().styleD(Sid::spatium);
}
textSizeScaling = std::max(textSizeScaling, 1.0);
instrumentNameOffset *= textSizeScaling;
system->mutldata()->setInstrumentNameOffset(instrumentNameOffset);
}
void SystemHeaderLayout::computeGroupBracketsWidths(System* system, LayoutContext& ctx)
{
System::LayoutData* ldata = system->mutldata();
ldata->clearGroupBracketsWidth();
std::vector groupBrackets;
for (Bracket* bracket : system->brackets()) {
if (bracket->bracketType() == BracketType::GROUP) {
groupBrackets.push_back(bracket);
}
}
if (groupBrackets.empty()) {
return;
}
std::sort(groupBrackets.begin(), groupBrackets.end(), [](Bracket* b1, Bracket* b2) { return b1->column() < b2->column(); });
bool hangIntoMargin = ctx.conf().styleB(Sid::groupBracketHangTextIntoMargin)
&& ctx.conf().styleV(Sid::groupBracketTextOrientation).value() == Orientation::VERTICAL;
double bracketDistToNames = ctx.conf().styleAbsolute(Sid::groupBracketDistanceToNames);
for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
Bracket* firstBracketOnThisStaff = nullptr;
for (Bracket* b : groupBrackets) {
if (b->contains(staffIdx)) {
firstBracketOnThisStaff = b;
break;
}
}
if (!firstBracketOnThisStaff) {
continue;
}
std::vector stack;
double bracketWidth = 0.0;
computeStackedBracketsWidth(firstBracketOnThisStaff, groupBrackets, bracketWidth, stack);
if (hangIntoMargin) {
bracketWidth += stack.back()->ldata()->bbox().left();
}
ldata->setGroupBracketsWidthAtStaffIdx(staffIdx, bracketWidth + bracketDistToNames);
}
if (!ctx.conf().styleB(Sid::instrumentNamesAlignIncludeGroupBrackets)) {
double maxGroupBracketWidth = 0.0;
for (const auto& pair : ldata->groupBracketsWidth()) {
maxGroupBracketWidth = std::max(maxGroupBracketWidth, pair.second);
}
for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
ldata->setGroupBracketsWidthAtStaffIdx(staffIdx, maxGroupBracketWidth);
}
}
}
void SystemHeaderLayout::computeStackedBracketsWidth(Bracket* first, const std::vector& allGroupBracketsOrderedByColumn,
double& width, std::vector& stack)
{
stack.push_back(first);
width += first->ldata()->bbox().width();
double bracketDist = first->style().styleAbsolute(Sid::groupBracketDistanceToGroupBracket);
for (Bracket* b2 : allGroupBracketsOrderedByColumn) {
if (muse::contains(stack, b2)) {
continue;
}
if (b2->column() > first->column() && b2->intersects(first)) {
stack.push_back(b2);
computeStackedBracketsWidth(b2, allGroupBracketsOrderedByColumn, width, stack);
width += bracketDist;
}
}
}
double SystemHeaderLayout::nameWidthIncludingGroupBrackets(InstrumentName* name, System* system)
{
const System::LayoutData* ldata = system->ldata();
double groupBracketsWidth = ldata->groupBracketsWidthAtStaffIdx(name->effectiveStaffIdx());
double result = name->width();
if (!muse::RealIsNull(groupBracketsWidth)) {
result += groupBracketsWidth;
}
return result;
}
void SystemHeaderLayout::computeInstrumentNamesWidth(System* system, LayoutContext& ctx)
{
computeGroupBracketsWidths(system, ctx);
System::LayoutData* ldata = system->mutldata();
ldata->setFirstColumnWidth(0.0);
ldata->setSecondColumnWidth(0.0);
ldata->setTotalNamesWidth(0.0);
std::unordered_set partsWithIndividualStaffNames;
std::unordered_set partsWithGroupNames;
std::vector groupNames;
std::vector instrumentNames;
std::vector sharedStaveNames;
for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
const SysStaff* sysStaff = system->staff(staffIdx);
const Staff* staff = ctx.dom().staff(staffIdx);
if (InstrumentName* groupName = sysStaff->name(InstrumentNameRole::GROUP)) {
if (ldata->useLongNames() && groupName->effectiveStaffIdx() == muse::nidx) {
// NOTE: We can only do this for long-name systems because they don't need to have
// the left barline aligned with the other systems across the page.
groupName->mutldata()->setIsSkipDraw(true);
continue;
}
groupName->mutldata()->setIsSkipDraw(false);
groupNames.push_back(groupName);
groupName->mutldata()->setColumn(1);
for (staff_idx_t groupIdx = groupName->staffIdx(); groupIdx < groupName->ldata()->endIdxOfGroup(); ++groupIdx) {
partsWithGroupNames.insert(ctx.dom().staff(groupIdx)->part());
}
ldata->setSecondColumnWidth(std::max(ldata->secondColumnWidth(), nameWidthIncludingGroupBrackets(groupName, system)));
ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), nameWidthIncludingGroupBrackets(groupName, system)));
}
if (!staff->show()) {
// We know that the staff is hidden in the entire score so safe to skip
continue;
}
if (InstrumentName* name = sysStaff->name(InstrumentNameRole::STAFF)) {
if (ldata->useLongNames() && !sysStaff->show()) {
// NOTE: We can only do this for long-name systems because they don't need to have
// the left barline aligned with the other systems across the page.
name->mutldata()->setIsSkipDraw(true);
continue;
}
name->mutldata()->setIsSkipDraw(false);
name->mutldata()->setColumn(0);
partsWithIndividualStaffNames.insert(ctx.dom().staff(staffIdx)->part());
ldata->setFirstColumnWidth(std::max(ldata->firstColumnWidth(), name->width()));
ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), nameWidthIncludingGroupBrackets(name, system)));
}
}
for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
const SysStaff* sysStaff = system->staff(staffIdx);
const Staff* staff = ctx.dom().staff(staffIdx);
Part* part = staff->part();
if (!part->show() || part->visibleStavesCount() == 0) {
// We know that the part is hidden in the entire score so safe to skip
continue;
}
InstrumentName* instrName = sysStaff->name(InstrumentNameRole::PART);
if (!instrName) {
instrName = sysStaff->name(InstrumentNameRole::SHARED_STAFF);
}
if (!instrName) {
continue;
}
if (ldata->useLongNames() && instrName->effectiveStaffIdx() == muse::nidx) {
// NOTE: We can only do this for long-name systems because they don't need to have
// the left barline aligned with the other systems across the page.
instrName->mutldata()->setIsSkipDraw(true);
continue;
}
instrName->mutldata()->setIsSkipDraw(false);
if (instrName->instrumentNameRole() == InstrumentNameRole::PART) {
instrumentNames.push_back(instrName);
} else {
sharedStaveNames.push_back(instrName);
}
ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), nameWidthIncludingGroupBrackets(instrName, system)));
if (partsWithGroupNames.count(part)) {
instrName->mutldata()->setColumn(0);
ldata->setFirstColumnWidth(std::max(ldata->firstColumnWidth(), instrName->width()));
} else {
instrName->mutldata()->setColumn(1);
ldata->setSecondColumnWidth(std::max(ldata->secondColumnWidth(), nameWidthIncludingGroupBrackets(instrName, system)));
}
}
auto sumWidth = [&](InstrumentName* outerName, InstrumentName* innerName) {
AlignH staffNameAlign = innerName->position();
if (staffNameAlign == AlignH::LEFT || staffNameAlign == AlignH::JUSTIFY) {
return ldata->firstColumnWidth() + nameWidthIncludingGroupBrackets(outerName, system) + ldata->instrumentNameOffset();
} else if (staffNameAlign == AlignH::HCENTER) {
double sumWidth = innerName->width() + nameWidthIncludingGroupBrackets(outerName, system) + ldata->instrumentNameOffset();
double move = 0.5 * (ldata->firstColumnWidth() - innerName->width());
return sumWidth + move;
} else {
return innerName->width() + ldata->instrumentNameOffset() + nameWidthIncludingGroupBrackets(outerName, system);
}
};
for (InstrumentName* sharedStaffName : sharedStaveNames) {
if (sharedStaffName->ldata()->column() == 0) {
continue;
}
Part* part = ctx.dom().staff(sharedStaffName->staffIdx())->part();
for (staff_idx_t idx : part->staveIdxList()) {
if (InstrumentName* n = system->staff(idx)->name(InstrumentNameRole::STAFF); n && !n->ldata()->isSkipDraw()) {
ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), sumWidth(sharedStaffName, n)));
}
}
}
if (stackLabelsVertically(system)) {
return;
}
for (InstrumentName* groupName : groupNames) {
staff_idx_t startStaff = groupName->staffIdx();
staff_idx_t endStaff = groupName->ldata()->endIdxOfGroup();
if (ldata->useLongNames() && system->visiblePartsOfGroup(startStaff, endStaff).size() % 2 == 0) {
continue;
}
for (staff_idx_t idx = startStaff; idx < endStaff; ++idx) {
if (InstrumentName* n = system->staff(idx)->name(InstrumentNameRole::STAFF); n && !n->ldata()->isSkipDraw()) {
ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), sumWidth(groupName, n)));
}
if (InstrumentName* n = system->staff(idx)->name(InstrumentNameRole::PART); n && !n->ldata()->isSkipDraw()) {
ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), sumWidth(groupName, n)));
}
}
}
for (InstrumentName* instrName : instrumentNames) {
if (instrName->ldata()->column() == 0) {
continue;
}
Part* part = ctx.dom().staff(instrName->staffIdx())->part();
if (ldata->useLongNames() && system->visibleStavesOfPart(part).size() % 2 == 0) {
continue;
}
for (staff_idx_t idx : part->staveIdxList()) {
if (InstrumentName* n = system->staff(idx)->name(InstrumentNameRole::STAFF); n && !n->ldata()->isSkipDraw()) {
ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), sumWidth(instrName, n)));
}
}
}
}
void SystemHeaderLayout::setInstrumentNamesVerticalPos(System* system, LayoutContext& ctx)
{
std::set partsWithIndividualStaffNames;
for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
SysStaff* s = system->staff(staffIdx);
for (InstrumentNameRole role : { InstrumentNameRole::STAFF, InstrumentNameRole::SHARED_STAFF }) {
if (InstrumentName* individualName = s->name(role)) {
const RectF& staffBBox = s->bbox();
const RectF& nameBBox = individualName->ldata()->bbox();
individualName->mutldata()->setPosY(0.5 * (staffBBox.top() + staffBBox.bottom() - nameBBox.bottom() - nameBBox.top()));
partsWithIndividualStaffNames.insert(ctx.dom().staff(staffIdx)->part());
}
}
}
bool stackVertically = stackLabelsVertically(system);
staff_idx_t staffIdx = 0;
for (const Part* p : ctx.dom().parts()) {
size_t nstaves = p->nstaves();
SysStaff* s = system->staff(staffIdx);
InstrumentName* t = s->name(InstrumentNameRole::PART);
if (!t || t->effectiveStaffIdx() == muse::nidx) {
staffIdx += nstaves;
continue;
}
const RectF& bbox = t->ldata()->bbox();
double yCenter = 0.5 * (bbox.bottom() + bbox.top());
std::vector visibleStavesOfPart = system->visibleStavesOfPart(p);
size_t visibleStavesCount = visibleStavesOfPart.size();
DO_ASSERT(visibleStavesCount > 0);
double y1 = 0;
double y2 = 0;
if (!muse::contains(partsWithIndividualStaffNames, p)) {
SysStaff* topSt = system->staff(visibleStavesOfPart.front());
SysStaff* bottomSt = system->staff(visibleStavesOfPart.back());
y1 = topSt->bbox().top();
y2 = bottomSt->bbox().bottom();
t->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
staffIdx += nstaves;
continue;
}
if (visibleStavesCount % 2 == 0) {
SysStaff* staffAboveMid = system->staff(visibleStavesOfPart[visibleStavesCount / 2 - 1]);
SysStaff* staffBelowMid = system->staff(visibleStavesOfPart[visibleStavesCount / 2]);
y1 = staffAboveMid->bbox().top();
y2 = staffBelowMid->bbox().bottom();
t->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
staffIdx += nstaves;
continue;
}
SysStaff* midStaff = system->staff(visibleStavesOfPart[visibleStavesCount / 2]);
y1 = midStaff->bbox().top();
y2 = midStaff->bbox().bottom();
if (InstrumentName* staffName = midStaff->name(InstrumentNameRole::STAFF)) {
if (stackVertically || t->ldata()->column() == 0) {
double lineSpacing = t->lineSpacing();
double instrNameBottom = t->ldata()->blocks.back().y();
double centerY = 0.5 * (midStaff->bbox().top() + midStaff->bbox().bottom());
t->mutldata()->setPosY(centerY - instrNameBottom - 0.25 * lineSpacing);
double staffNameTop = staffName->ldata()->blocks.front().y();
staffName->mutldata()->setPosY(centerY - staffNameTop + 0.75 * lineSpacing);
} else if (staffName->ldata()->rows() == 1 && t->ldata()->rows() == 1) {
t->mutldata()->setPosY(staffName->y());
} else {
t->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
}
}
staffIdx += nstaves;
}
for (staff_idx_t idx = 0; idx < system->staves().size(); ++idx) {
InstrumentName* groupName = system->staff(idx)->name(InstrumentNameRole::GROUP);
if (!groupName || groupName->effectiveStaffIdx() == muse::nidx) {
continue;
}
const RectF& bbox = groupName->ldata()->bbox();
double yCenter = 0.5 * (bbox.bottom() + bbox.top());
std::vector visibleParts = system->visiblePartsOfGroup(idx, groupName->ldata()->endIdxOfGroup());
size_t visiblePartsCount = visibleParts.size();
DO_ASSERT(visiblePartsCount > 0);
double y1 = 0;
double y2 = 0;
if (visiblePartsCount % 2 == 0) {
Part* partAboveMid = visibleParts[visiblePartsCount / 2 - 1];
staff_idx_t staffIdxAboveMid = system->lastVisibleSysStaffOfPart(partAboveMid);
DO_ASSERT(staffIdxAboveMid != muse::nidx);
SysStaff* staffAboveMid = system->staff(staffIdxAboveMid);
Part* partBelowMid = visibleParts[visiblePartsCount / 2];
staff_idx_t staffIdxBelowMid = system->firstVisibleSysStaffOfPart(partBelowMid);
DO_ASSERT(staffIdxBelowMid != muse::nidx);
SysStaff* staffBelowMid = system->staff(staffIdxBelowMid);
y1 = staffAboveMid->bbox().top();
y2 = staffBelowMid->bbox().bottom();
groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
continue;
}
Part* midPart = visibleParts[visiblePartsCount / 2];
InstrumentName* instrName = system->staff(*midPart->staveIdxList().begin())->name(InstrumentNameRole::PART);
std::vector visibleStaves = system->visibleStavesOfPart(midPart);
size_t visibleStavesCount = visibleStaves.size();
if (visibleStavesCount % 2) {
SysStaff* midStaff = system->staff(visibleStaves[visibleStavesCount / 2]);
y1 = midStaff->bbox().top();
y2 = midStaff->bbox().bottom();
} else {
SysStaff* staffAboveMid = system->staff(visibleStaves[visibleStavesCount / 2 - 1]);
SysStaff* staffBelowMid = system->staff(visibleStaves[visibleStavesCount / 2]);
y1 = staffAboveMid->bbox().top();
y2 = staffBelowMid->bbox().bottom();
}
if (instrName) {
if (stackVertically) {
double lineSpacing = groupName->lineSpacing();
double groupNameBottom = groupName->ldata()->blocks.back().y();
if (visibleStavesCount % 2 && system->staff(visibleStaves[visibleStavesCount / 2])->name(InstrumentNameRole::STAFF)) {
groupName->mutldata()->setPosY(instrName->y() - groupNameBottom - lineSpacing);
} else {
double centerY = 0.5 * (y1 + y2);
groupName->mutldata()->setPosY(centerY - groupNameBottom - 0.25 * lineSpacing);
double instrNameTop = instrName->ldata()->blocks.front().y();
instrName->mutldata()->setPosY(centerY - instrNameTop + 0.75 * lineSpacing);
}
} else if (instrName->ldata()->rows() == 1 && groupName->ldata()->rows() == 1) {
groupName->mutldata()->setPosY(instrName->y());
} else {
groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
}
continue;
}
if (visibleStavesCount % 2 == 0) {
groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
continue;
}
SysStaff* midStaff = system->staff(visibleStaves[visibleStavesCount / 2]);
InstrumentName* staffName = midStaff->name(InstrumentNameRole::STAFF);
if (!staffName) {
groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
continue;
}
if (stackVertically) {
double lineSpacing = groupName->lineSpacing();
double groupNameBottom = groupName->ldata()->blocks.back().y();
double centerY = 0.5 * (y1 + y2);
groupName->mutldata()->setPosY(centerY - groupNameBottom - 0.25 * lineSpacing);
double staffNameTop = staffName->ldata()->blocks.front().y();
staffName->mutldata()->setPosY(centerY - staffNameTop + 0.75 * lineSpacing);
} else if (staffName->ldata()->rows() == 1 && groupName->ldata()->rows() == 1) {
groupName->mutldata()->setPosY(staffName->y());
} else {
groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
}
}
}
void SystemHeaderLayout::setInstrumentNamesHorizontalPos(System* system)
{
//---------------------------------------------------
// layout instrument names x position
// at this point it is not clear which staves will
// be hidden, so layout all instrument names
//---------------------------------------------------
System::LayoutData* ldata = system->mutldata();
double totalNamesWidth = ldata->totalNamesWidth();
double firstColumnWidth = ldata->firstColumnWidth();
InstrumentNamesAlign align = system->style().styleV(
ldata->useLongNames() ? Sid::instrumentNamesAlignLong : Sid::instrumentNamesAlignShort).value();
auto placeFirstColumnName = [&](InstrumentName* name) {
const RectF& bbox = name->ldata()->bbox();
if (align == InstrumentNamesAlign::CENTER_CENTER) {
double groupBracketsWidth = ldata->groupBracketsWidthAtStaffIdx(name->effectiveStaffIdx());
name->mutldata()->setPosX(0.5 * totalNamesWidth - 0.5 * (bbox.right() + bbox.left()) + groupBracketsWidth);
} else {
switch (name->position()) {
case AlignH::JUSTIFY:
case AlignH::LEFT:
name->mutldata()->setPosX(totalNamesWidth - firstColumnWidth - bbox.left());
break;
case AlignH::HCENTER:
name->mutldata()->setPosX(totalNamesWidth - 0.5 * firstColumnWidth - 0.5 * (bbox.right() + bbox.left()));
break;
case AlignH::RIGHT:
name->mutldata()->setPosX(totalNamesWidth - bbox.right());
}
}
};
for (const SysStaff* s : system->staves()) {
for (InstrumentNameRole role : { InstrumentNameRole::STAFF,
InstrumentNameRole::SHARED_STAFF,
InstrumentNameRole::PART }) {
InstrumentName* n = s->name(role);
if (n && n->ldata()->column() == 0 && n->effectiveStaffIdx() != muse::nidx) {
placeFirstColumnName(n);
}
}
}
bool stackVertically = stackLabelsVertically(system);
auto placeSecondColumnName = [&](InstrumentName* name, staff_idx_t staffIdx) {
bool doStackVertically = stackVertically && name->instrumentNameRole() != InstrumentNameRole::SHARED_STAFF;
const RectF& bbox = name->ldata()->bbox();
double groupBracketsWidth = ldata->groupBracketsWidthAtStaffIdx(name->effectiveStaffIdx());
if (align == InstrumentNamesAlign::LEFT_RIGHT) {
name->mutldata()->setPosX(groupBracketsWidth - bbox.left());
return;
}
if (align == InstrumentNamesAlign::CENTER_CENTER) {
name->mutldata()->setPosX(0.5 * totalNamesWidth - 0.5 * (bbox.right() + bbox.left()) + 0.5 * groupBracketsWidth);
return;
}
if (align == InstrumentNamesAlign::CENTER_RIGHT) {
name->mutldata()->setPosX(0.5 * ldata->secondColumnWidth() - 0.5 * (bbox.right() + bbox.left()) + 0.5 * groupBracketsWidth);
return;
}
if (doStackVertically) {
name->mutldata()->setPosX(totalNamesWidth - bbox.right());
return;
}
if (name->instrumentNameRole() == InstrumentNameRole::SHARED_STAFF) {
if (InstrumentName* staffName = system->staff(name->staffIdx())->name(InstrumentNameRole::STAFF)) {
name->mutldata()->setPosX(
staffName->x() + staffName->ldata()->bbox().left() - bbox.right() - ldata->instrumentNameOffset());
} else {
name->mutldata()->setPosX(totalNamesWidth - bbox.right());
}
return;
}
if (name->instrumentNameRole() == InstrumentNameRole::PART) {
const Part* p = system->score()->staff(staffIdx)->part();
std::vector visibleStavesForPart = system->visibleStavesOfPart(p);
size_t visibleStaveCount = visibleStavesForPart.size();
if (visibleStaveCount % 2 == 0) {
name->mutldata()->setPosX(totalNamesWidth - bbox.right());
return;
}
staff_idx_t centerStaff = visibleStavesForPart[visibleStaveCount / 2];
if (InstrumentName* staffName = system->staff(centerStaff)->name(InstrumentNameRole::STAFF)) {
name->mutldata()->setPosX(
staffName->x() + staffName->ldata()->bbox().left() - bbox.right() - ldata->instrumentNameOffset());
} else {
name->mutldata()->setPosX(totalNamesWidth - bbox.right());
}
return;
}
std::vector visiblePartsOfGroup = system->visiblePartsOfGroup(staffIdx, name->ldata()->endIdxOfGroup());
size_t visiblePartsCount = visiblePartsOfGroup.size();
if (visiblePartsCount % 2 == 0) {
name->mutldata()->setPosX(totalNamesWidth - bbox.right());
return;
}
Part* centerPart = visiblePartsOfGroup[visiblePartsCount / 2];
if (InstrumentName* instrName = system->staff(*centerPart->staveIdxList().begin())->name(InstrumentNameRole::PART)) {
name->mutldata()->setPosX(
instrName->x() + instrName->ldata()->bbox().left() - bbox.right() - ldata->instrumentNameOffset());
return;
}
std::vector visibleStavesOfPart = system->visibleStavesOfPart(centerPart);
size_t visibleStavesCount = visibleStavesOfPart.size();
if (visibleStavesCount % 2 == 0) {
name->mutldata()->setPosX(totalNamesWidth - bbox.right());
return;
}
staff_idx_t centerStaff = visibleStavesOfPart[visibleStavesCount / 2];
if (InstrumentName* staffName = system->staff(centerStaff)->name(InstrumentNameRole::STAFF)) {
name->mutldata()->setPosX(
staffName->x() + staffName->ldata()->bbox().left() - bbox.right() - ldata->instrumentNameOffset());
} else {
name->mutldata()->setPosX(totalNamesWidth - bbox.right());
}
};
for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
const SysStaff* s = system->staff(staffIdx);
InstrumentName* instrName = s->name(InstrumentNameRole::PART);
if (!instrName) {
instrName = s->name(InstrumentNameRole::SHARED_STAFF);
}
if (!instrName) {
continue;
}
if (instrName && instrName->effectiveStaffIdx() != muse::nidx && instrName->ldata()->column() > 0) {
placeSecondColumnName(instrName, staffIdx);
}
}
for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
const SysStaff* s = system->staff(staffIdx);
InstrumentName* groupName = s->name(InstrumentNameRole::GROUP);
if (groupName && groupName->effectiveStaffIdx() != muse::nidx) {
placeSecondColumnName(groupName, staffIdx);
}
}
}
void SystemHeaderLayout::setGroupBracketsHorizontalPos(System* system)
{
std::vector groupBrackets;
for (Bracket* b : system->brackets()) {
if (b->bracketType() == BracketType::GROUP) {
groupBrackets.push_back(b);
}
}
std::sort(groupBrackets.begin(), groupBrackets.end(), [](Bracket* b1, Bracket* b2) { return b1->column() > b2->column(); });
const MStyle& style = system->style();
bool intoMargin = style.styleB(Sid::groupBracketHangTextIntoMargin)
&& style.styleV(Sid::groupBracketTextOrientation).value() == Orientation::VERTICAL;
double bracketsDist = style.styleAbsolute(Sid::groupBracketDistanceToGroupBracket);
for (Bracket* b : groupBrackets) {
b->mutldata()->setPosX(intoMargin ? 0.0 : -b->mutldata()->bbox().left());
for (Bracket* bb : groupBrackets) {
if (bb == b) {
break;
}
if (bb->column() > b->column() && bb->intersects(b)) {
b->mutldata()->setPosX(bb->x() + bb->ldata()->bbox().right() - b->ldata()->bbox().left() + bracketsDist);
}
}
}
}
InstrumentName* SystemHeaderLayout::updateName(System* system, staff_idx_t staffIdx, LayoutContext& ctx, const String& name,
InstrumentNameType type, InstrumentNameRole role)
{
SysStaff* sysStaff = system->staff(staffIdx);
InstrumentName* iname = sysStaff->name(role);
if (name.empty()) {
if (iname) {
ctx.mutDom().removeElement(iname);
}
return nullptr;
}
if (!iname) {
iname = new InstrumentName(system);
iname->setGenerated(true);
iname->setOwnershipParent(system);
iname->setSysStaff(sysStaff);
iname->setTrack(staffIdx * VOICES);
iname->setInstrumentNameType(type);
iname->setInstrumentNameRole(role);
ctx.mutDom().addElement(iname);
}
iname->setAlign(Align(iname->align().horizontal, AlignV::BASELINE));
iname->setXmlText(name);
iname->mutldata()->setColumn(0); // Reset here, will be computed later
TLayout::layoutInstrumentName(iname, iname->mutldata());
return iname;
}
String SystemHeaderLayout::formattedInstrumentName(System* system, Part* part, const Fraction& tick)
{
bool longNames = system->ldata()->useLongNames();
Instrument* instr = part->instrument(tick);
const InstrumentLabel& label = instr->instrumentLabel();
const MStyle& style = system->style();
bool trailingDot = style.styleB(Sid::instrumentNumeralsTrailingDotSingle);
if (muse::contains(system->ldata()->partsWithGroupName(), part)) {
if (label.useCustomIndividualName()) {
return longNames ? label.customNameLongIndividual() : label.customNameShortIndividual();
}
int number = part->number(tick);
String result = number > 0 ? String::number(number) : String();
if (!result.isEmpty() && trailingDot) {
result += '.';
}
return result;
}
if (label.useCustomName()) {
return longNames ? label.customNameLong() : label.customNameShort();
}
String instrName = longNames ? instr->longName() : instr->shortName();
bool showTranspo = longNames ? label.showTranspositionLong() : label.showTranspositionShort();
showTranspo &= style.styleB(longNames ? Sid::instrumentNamesShowTranspositionLong : Sid::instrumentNamesShowTranspositionShort);
String transposition = showTranspo ? instr->transposition() : String();
bool showNumber = longNames ? label.showNumberLong() : label.showNumberShort();
String number = instr->number() > 0 && showNumber ? String::number(instr->number()) : String();
InstrumentNamesFormat nameFormat
= style.styleV(longNames ? Sid::instrumentNamesFormatLong : Sid::instrumentNamesFormatShort).value();
bool addTrailingDot = trailingDot && (transposition.empty() || nameFormat == InstrumentNamesFormat::NAME_IN_TRANSP_NUM
|| nameFormat == InstrumentNamesFormat::TRANSP_NAME_NUM);
if (addTrailingDot) {
number += '.';
}
if (transposition.empty()) {
String result = instrName;
if (!number.empty()) {
result += u" " + number;
}
return result;
}
//: For instrument transposition, e.g. Horn in F
String in = TranslatableString("notation", "in").translated();
switch (nameFormat) {
case InstrumentNamesFormat::NAME_IN_TRANSP_NUM:
return instrName + u" " + in + u" " + transposition + (number.empty() ? String() : u" " + number);
case InstrumentNamesFormat::NAME_NUM_IN_TRANSP:
return instrName + (number.empty() ? String() : u" " + number) + u" " + in + u" " + transposition;
case InstrumentNamesFormat::TRANSP_NAME_NUM:
return transposition + u" " + instrName + (number.empty() ? String() : u" " + number);
default:
{
String result = style.styleSt(longNames ? Sid::instrumentNamesCustomFormatLong : Sid::instrumentNamesCustomFormatShort);
return resolveTokens(result, instrName, transposition, number);
}
}
}
String SystemHeaderLayout::formattedGroupName(System* system, Part* part, const Fraction& tick)
{
const MStyle& style = system->style();
bool longNames = system->ldata()->useLongNames();
Instrument* instr = part->instrument(tick);
const InstrumentLabel& label = instr->instrumentLabel();
if (label.useCustomGroupName()) {
return longNames ? label.customNameLongGroup() : label.customNameShortGroup();
}
String instrName = longNames ? instr->longName() : instr->shortName();
bool showTranspo = longNames ? label.showTranspositionLong() : label.showTranspositionShort();
showTranspo &= style.styleB(longNames ? Sid::instrumentNamesShowTranspositionLong : Sid::instrumentNamesShowTranspositionShort);
String transposition = showTranspo ? instr->transposition() : String();
if (transposition.empty()) {
return instrName;
}
InstrumentNamesFormat nameFormat
= style.styleV(longNames ? Sid::instrumentNamesFormatLong : Sid::instrumentNamesFormatShort).value();
//: For instrument transposition, e.g. Horn in F
String in = TranslatableString("notation", "in").translated();
switch (nameFormat) {
case InstrumentNamesFormat::NAME_IN_TRANSP_NUM:
return instrName + u" " + in + u" " + transposition;
case InstrumentNamesFormat::NAME_NUM_IN_TRANSP:
return instrName + u" " + in + u" " + transposition;
case InstrumentNamesFormat::TRANSP_NAME_NUM:
return transposition + u" " + instrName;
default:
{
String result = style.styleSt(longNames ? Sid::instrumentNamesCustomFormatLong : Sid::instrumentNamesCustomFormatShort);
return resolveTokens(result, instrName, transposition, /*number*/ String());
}
}
}
Part* SystemHeaderLayout::originPartForStaff(staff_idx_t staffIdx, const SharedTrackMap& trackMap,
const std::vector& originParts)
{
if (originParts.empty()) {
return nullptr;
}
Score* score = originParts.front()->score();
for (auto [originTrack, sharedTrack] : trackMap) {
if (track2staff(sharedTrack) == staffIdx && track2voice(sharedTrack) == 0) {
return score->staff(track2staff(originTrack))->part();
}
}
return nullptr;
}
String SystemHeaderLayout::formattedSharedStaffLabel(staff_idx_t staffIdx, const SharedTrackMap& trackMap,
const std::vector& originParts)
{
if (originParts.empty()) {
return String();
}
Score* score = originParts.front()->score();
const MStyle& style = score->style();
bool trailingDotSingle = style.styleB(Sid::instrumentNumeralsTrailingDotSingle);
bool trailingDotMultiple = style.styleB(Sid::instrumentNumeralsTrailingDotMultiple);
SharedLabelOrientation orientation = style.styleV(Sid::instrumentNumeralsOrientation).value();
int horizontalLimit = style.styleI(Sid::instrumentNumeralsHorizontalThreshold);
int verticalLimit = style.styleI(Sid::instrumentNumeralsVerticalThreshold);
int hyphenLimit = style.styleB(Sid::instrumentNumeralsHyphenEnable) ? style.styleI(Sid::instrumentNumeralsHyphenThreshold) : INT_MAX;
std::vector instrumentsMappedToFirstVoice;
std::vector instrumentsMappedToSecondVoice;
for (auto [originTrack, sharedTrack] : trackMap) {
if (track2staff(sharedTrack) == staffIdx) {
if (track2voice(sharedTrack) == 0) {
instrumentsMappedToFirstVoice.push_back(score->staff(track2staff(originTrack))->part()->instrument());
} else {
instrumentsMappedToSecondVoice.push_back(score->staff(track2staff(originTrack))->part()->instrument());
}
}
}
size_t totInstrumentCount = instrumentsMappedToFirstVoice.size() + instrumentsMappedToSecondVoice.size();
SharedLabelOrientation actualOrientation;
switch (orientation) {
case SharedLabelOrientation::VOICE:
actualOrientation = orientation;
break;
case SharedLabelOrientation::HORIZONTAL:
actualOrientation = totInstrumentCount > size_t(horizontalLimit) ? SharedLabelOrientation::VOICE : orientation;
break;
case SharedLabelOrientation::VERTICAL:
actualOrientation = totInstrumentCount > size_t(verticalLimit) ? SharedLabelOrientation::VOICE : orientation;
break;
default: // should not happen, but don't leave actualOrientation uninitialized
ASSERT_X("Unexpected SharedLabelOrientation value: " << static_cast(orientation));
actualOrientation = SharedLabelOrientation::VOICE;
break;
}
if (actualOrientation == SharedLabelOrientation::HORIZONTAL || actualOrientation == SharedLabelOrientation::VERTICAL) {
// Put all instruments in single vector
instrumentsMappedToFirstVoice.insert(instrumentsMappedToFirstVoice.end(),
instrumentsMappedToSecondVoice.begin(), instrumentsMappedToSecondVoice.end());
std::sort(instrumentsMappedToFirstVoice.begin(), instrumentsMappedToFirstVoice.end(),
[](const Instrument* a, const Instrument* b) { return a->number() < b->number(); });
String result;
if (actualOrientation == SharedLabelOrientation::HORIZONTAL) {
result = formatSharedVoiceLabel(instrumentsMappedToFirstVoice, trailingDotSingle, trailingDotMultiple, hyphenLimit);
} else {
result = formatVerticalSharedLabel(instrumentsMappedToFirstVoice, trailingDotSingle);
}
return result;
}
String result = formatSharedVoiceLabel(instrumentsMappedToFirstVoice, trailingDotSingle, trailingDotMultiple, hyphenLimit);
if (instrumentsMappedToSecondVoice.empty()) {
return result;
}
result += '\n' + formatSharedVoiceLabel(instrumentsMappedToSecondVoice, trailingDotSingle, trailingDotMultiple, hyphenLimit);
return result;
}
String SystemHeaderLayout::formatVerticalSharedLabel(const std::vector& instruments, bool trailingDotSingle)
{
String result;
for (const Instrument* instr : instruments) {
if (!result.empty()) {
result += '\n';
}
result += String::number(instr->number());
if (trailingDotSingle) {
result += '.';
}
}
return result;
}
String SystemHeaderLayout::formatSharedVoiceLabel(const std::vector& instruments, bool trailingDotSingle,
bool trailingDotMultiple, int hyphenLimit)
{
String result;
size_t voiceCount = instruments.size();
bool putTrailingDot = (voiceCount <= 1 && trailingDotSingle) || (voiceCount > 1 && trailingDotMultiple);
for (size_t i = 0; i < instruments.size(); ++i) {
if (!result.empty() && result.back() != '\n') {
result += '.';
}
const Instrument* instr = instruments[i];
int startNumber = instr->number();
int curNumber = startNumber;
for (size_t j = i + 1; j < instruments.size(); ++j) {
const Instrument* nextInstr = instruments[j];
int nextNumber = nextInstr->number();
if (nextNumber == curNumber + 1) {
curNumber = nextNumber;
if (nextNumber - startNumber >= hyphenLimit) {
i = j;
} else {
continue;
}
} else {
break;
}
}
if (curNumber - startNumber >= hyphenLimit) {
result += String::number(startNumber);
if (putTrailingDot) {
result += '.';
}
result += u'–' + String::number(curNumber);
} else {
result += String::number(startNumber);
}
}
if (!result.isEmpty() && putTrailingDot) {
result += u".";
}
return result;
}
String& SystemHeaderLayout::resolveTokens(String& str, const String& name, const String& transposition, const String& number)
{
static const String NAME = u"$name";
static const String TRANSP = u"$transposition";
static const String NUMBER = u"$number";
str.replace(NAME, name);
str.replace(TRANSP, transposition);
if (!number.empty()) {
str.replace(NUMBER, number);
} else if (str.contains(u" $number")) {
str.remove(u" $number");
} else if (str.contains(u"$number ")) {
str.remove(u"$number ");
} else if (str.contains(u"$number")) {
str.remove(u"$number");
}
return str;
}
bool SystemHeaderLayout::showNames(LayoutContext& ctx)
{
if (!ctx.conf().isShowInstrumentNames()) {
return false;
}
if (ctx.conf().styleB(Sid::hideInstrumentNameIfOneInstrument) && ctx.dom().visiblePartCount() <= 1) {
std::vector visibleParts = ctx.dom().visibleParts();
if (visibleParts.size() == 0) {
return false;
}
if (visibleParts.size() == 1 && !visibleParts.front()->isSharedPart()) {
return false;
}
}
if (ctx.state().firstSystem()
&& ctx.conf().styleV(Sid::firstSystemInstNameVisibility).value() == InstrumentLabelVisibility::HIDE) {
return false;
}
if (!ctx.state().firstSystem()
&& ctx.conf().styleV(Sid::subsSystemInstNameVisibility).value() == InstrumentLabelVisibility::HIDE) {
return false;
}
return true;
}
void SystemHeaderLayout::setInstrumentNames(System* system, LayoutContext& ctx)
{
if (system->vbox()) {
return;
}
const Measure* firstMeasure = system->firstMeasure();
if (!firstMeasure) {
return;
}
Fraction tick = firstMeasure->tick();
bool subsSysLongName = ctx.conf().styleV(Sid::subsSystemInstNameVisibility).value()
== InstrumentLabelVisibility::LONG;
bool longName = ctx.mutState().firstSystem() ? ctx.mutState().startWithLongNames() : subsSysLongName;
System::LayoutData* ldata = system->mutldata();
ldata->setUseLongNames(longName);
InstrumentNameType type = longName ? InstrumentNameType::LONG : InstrumentNameType::SHORT;
if (!showNames(ctx)) {
for (staff_idx_t idx = 0; idx < system->staves().size(); ++idx) {
updateName(system, idx, ctx, String(), type, InstrumentNameRole::STAFF);
updateName(system, idx, ctx, String(), type, InstrumentNameRole::SHARED_STAFF);
updateName(system, idx, ctx, String(), type, InstrumentNameRole::PART);
updateName(system, idx, ctx, String(), type, InstrumentNameRole::GROUP);
}
return;
}
updateGroupNames(system, ctx, tick);
for (size_t staffIdx = 0; staffIdx < system->staves().size(); /*empty*/) {
Part* part = ctx.dom().staff(staffIdx)->part();
size_t partNstaves = part->nstaves();
size_t visibleStavesCount = part->visibleStavesCount();
if (part->isSharedPart() && part->show()) {
setSharedPartNames(toSharedPart(part), staffIdx, system, ctx, longName, tick);
staffIdx += partNstaves;
continue;
}
for (size_t idxInPart = 0; idxInPart < partNstaves; ++idxInPart) {
staff_idx_t globalIdx = staffIdx + idxInPart;
String instrumentName;
if (idxInPart == 0 && part->show() && visibleStavesCount > 0) {
instrumentName = formattedInstrumentName(system, part, tick);
}
updateName(system, globalIdx, ctx, instrumentName, type, InstrumentNameRole::PART);
const Staff* staff = ctx.dom().staff(globalIdx);
String staffName;
if (staff->show()) {
staffName = longName ? staff->individualStaffNameLong(tick) : staff->individualStaffNameShort(tick);
}
updateName(system, globalIdx, ctx, staffName, type, InstrumentNameRole::STAFF);
}
staffIdx += partNstaves;
}
}
void SystemHeaderLayout::setSharedPartNames(SharedPart* sharedPart, staff_idx_t startStaffIdx, System* system, LayoutContext& ctx,
bool longName, const Fraction& tick)
{
InstrumentNameType type = longName ? InstrumentNameType::LONG : InstrumentNameType::SHORT;
if (!sharedPart->show() || sharedPart->visibleStavesCount() == 0) {
for (size_t relStaffIdx = 0; relStaffIdx < sharedPart->nstaves(); ++relStaffIdx) {
size_t globalStaffIdx = startStaffIdx + relStaffIdx;
updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::GROUP);
updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::PART);
updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::SHARED_STAFF);
updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::STAFF);
}
return;
}
if (!sharedPart->isSameInstruments()) {
// TODO
return;
}
const Instrument* instr = sharedPart->instrument();
bool useGroup = useGroupNames(instr->group(), ctx) && sharedPart->isSameInstrumentsAtTick(tick);
const SharedTrackMap& trackMap = sharedPart->trackMapAtTick(tick);
const std::vector originParts = sharedPart->originParts();
// If using group names, assign normal PART name (one name covers all shared staves). Obtains the style
//
// 1.2
// Horn
// 3.4
//
// If not using group names, assign SHARED_STAFF name to each stave. Obtains the style
//
// Horn 1.2
//
// Horn 3.4
for (size_t relStaffIdx = 0; relStaffIdx < sharedPart->nstaves(); ++relStaffIdx) {
size_t globalStaffIdx = startStaffIdx + relStaffIdx;
Part* originPart = originPartForStaff(globalStaffIdx, trackMap, originParts);
String staffGroupName = formattedGroupName(system, originPart ? originPart : sharedPart, tick);
if (useGroup) {
if (relStaffIdx == 0) {
updateName(system, globalStaffIdx, ctx, staffGroupName, type, InstrumentNameRole::PART);
}
updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::SHARED_STAFF);
} else {
updateName(system, globalStaffIdx, ctx, staffGroupName, type, InstrumentNameRole::SHARED_STAFF);
}
String staffLabel = formattedSharedStaffLabel(globalStaffIdx, trackMap, originParts);
updateName(system, globalStaffIdx, ctx, staffLabel, type, InstrumentNameRole::STAFF);
}
}
void SystemHeaderLayout::updateGroupNames(System* system, LayoutContext& ctx, const Fraction& tick)
{
InstrumentNameType type = system->ldata()->useLongNames() ? InstrumentNameType::LONG : InstrumentNameType::SHORT;
System::LayoutData* ldata = system->mutldata();
ldata->clearPartsWithGroupNames();
for (staff_idx_t startOfGroup = 0; startOfGroup < system->staves().size();) {
Part* curPart = ctx.dom().staff(startOfGroup)->part();
const Instrument* curInstrument = curPart->instrument(tick);
const InstrumentLabel& curLabel = curInstrument->instrumentLabel();
if (!curInstrument->instrumentLabel().allowGroupName() || !curPart->show() || curPart->isSharedPart()) {
++startOfGroup;
continue;
}
std::vector partsInThisGroup;
staff_idx_t endOfGroup = startOfGroup;
while (endOfGroup < system->staves().size()) {
Part* nextPart = ctx.dom().staff(endOfGroup)->part();
Instrument* nextInstrument = nextPart->instrument(tick);
InstrumentLabel& nextLabel = nextInstrument->instrumentLabel();
if (nextPart != curPart
&& (nextInstrument->id() != curInstrument->id() || !nextLabel.allowGroupName() || nextPart->isSharedPart())) {
break;
}
if (nextLabel.useCustomGroupName() && (nextLabel.customNameLongGroup() != curLabel.customNameLongGroup()
|| nextLabel.customNameShortGroup() != curLabel.customNameShortGroup())) {
break;
}
if (type == InstrumentNameType::LONG && system->visibleStavesOfPart(nextPart).size() == 0) {
// NOTE: We can only do this for long-name systems because they don't need to have
// the left barline aligned with the other systems across the page.
endOfGroup += nextPart->nstaves();
continue;
}
if (nextPart->show() && nextPart->visibleStavesCount() > 0) {
partsInThisGroup.push_back(nextPart);
nextLabel.setUseCustomGroupName(curLabel.useCustomGroupName());
nextLabel.setCustomNameLongGroup(curLabel.customNameLongGroup());
nextLabel.setCustomNameShortGroup(curLabel.customNameShortGroup());
}
endOfGroup += nextPart->nstaves();
}
if (partsInThisGroup.size() > 1 && useGroupNames(curInstrument->group(), ctx)) {
String name = formattedGroupName(system, curPart, tick);
InstrumentName* groupName = updateName(system, startOfGroup, ctx, name, type, InstrumentNameRole::GROUP);
if (groupName) {
groupName->mutldata()->setEndIdxOfGroup(endOfGroup);
for (Part* p : partsInThisGroup) {
ldata->addPartWithGroupNames(p, groupName);
}
}
for (staff_idx_t idx = startOfGroup + 1; idx < endOfGroup; ++idx) {
updateName(system, idx, ctx, String(), type, InstrumentNameRole::GROUP);
}
} else {
for (staff_idx_t idx = startOfGroup; idx < endOfGroup; ++idx) {
updateName(system, idx, ctx, String(), type, InstrumentNameRole::GROUP);
}
}
startOfGroup = endOfGroup;
}
for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
if (system->staff(staffIdx)->name(InstrumentNameRole::GROUP)) {
Part* part = ctx.dom().staff(staffIdx)->part();
if (!muse::contains(ldata->partsWithGroupName(), part)) {
updateName(system, staffIdx, ctx, String(), type, InstrumentNameRole::GROUP);
}
}
}
}
bool SystemHeaderLayout::useGroupNames(const String& instrumentGroup, LayoutContext& ctx)
{
const MStyle& style = ctx.conf().style();
if (instrumentGroup == "woodwinds" || instrumentGroup == "brass") {
return style.styleB(Sid::windsNameByGroup);
}
if (instrumentGroup == "vocals") {
return style.styleB(Sid::vocalsNameByGroup);
}
if (instrumentGroup == "strings") {
return style.styleB(Sid::stringsNameByGroup);
}
return style.styleB(Sid::othersNameByGroup);
}
void SystemHeaderLayout::updateSystemHeaderWidth(System* system, LayoutContext& ctx)
{
if (system->staves().empty()) { // ignore vbox
return;
}
SystemHeaderLayout::computeInstrumentNameOffset(system, ctx);
double instrumentNameOffset = system->ldata()->instrumentNameOffset();
//---------------------------------------------------
// find x position of staves
//---------------------------------------------------
SystemHeaderLayout::layoutBrackets(system, ctx);
double maxBracketsWidth = SystemHeaderLayout::totalBracketOffset(ctx);
SystemHeaderLayout::setInstrumentNames(system, ctx);
SystemHeaderLayout::computeInstrumentNamesWidth(system, ctx);
double maxNamesWidth = system->ldata()->totalNamesWidth();
double indent = maxNamesWidth > 0 ? maxNamesWidth + instrumentNameOffset : 0.0;
if (ctx.state().firstSystem() && ctx.state().firstSystemIndent()) {
indent = std::max(indent, system->styleP(Sid::firstSystemIndentationValue) * system->mag() - maxBracketsWidth);
maxNamesWidth = indent - instrumentNameOffset;
}
if (muse::RealIsNull(indent)) {
if (ctx.conf().styleB(Sid::alignSystemToMargin)) {
system->setLeftMargin(0.0);
} else {
system->setLeftMargin(maxBracketsWidth);
}
} else {
system->setLeftMargin(indent + maxBracketsWidth);
}
}