LCOV - code coverage report
Current view: top level - src/engraving/rendering/score - systemheaderlayout.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 74.7 % 984 735
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-Studio-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2023 MuseScore Limited
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "systemheaderlayout.h"
      24              : 
      25              : #include "systemlayout.h"
      26              : #include "tlayout.h"
      27              : 
      28              : #include "dom/bracket.h"
      29              : #include "dom/factory.h"
      30              : #include "dom/part.h"
      31              : #include "dom/staff.h"
      32              : #include "dom/system.h"
      33              : #include "style/defaultstyle.h"
      34              : 
      35              : using namespace mu::engraving;
      36              : using namespace mu::engraving::rendering::score;
      37              : 
      38        14371 : double SystemHeaderLayout::layoutBrackets(System* system, LayoutContext& ctx)
      39              : {
      40        14371 :     size_t nstaves = system->staves().size();
      41        14371 :     size_t columns = system->getBracketsColumnsCount();
      42              : 
      43        14371 :     std::vector<double> bracketWidth(columns, 0.0);
      44              : 
      45        14371 :     std::vector<Bracket*> bl;
      46        14371 :     bl.swap(system->brackets());
      47              : 
      48        36159 :     for (size_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
      49        33046 :         for (size_t i = 0; i < columns; ++i) {
      50        22916 :             for (BracketItem* bi : ctx.dom().brackets(staffIdx)) {
      51        11658 :                 if (bi->column() != i || bi->bracketType() == BracketType::NO_BRACKET) {
      52         8156 :                     continue;
      53              :                 }
      54         3502 :                 Bracket* b = createBracket(system, ctx, bi, i, static_cast<int>(staffIdx), bl, system->firstMeasure());
      55         3502 :                 if (b != nullptr) {
      56         2781 :                     b->mutldata()->bracketHeight.set_value(3.5 * b->spatium() * 2); // dummy
      57         2781 :                     TLayout::layoutBracket(b, b->mutldata(), ctx.conf());
      58         2781 :                     if (bi->bracketType() != BracketType::GROUP) { // Because Group brackets always stay outside
      59         2781 :                         bracketWidth[i] = std::max(bracketWidth[i], b->ldata()->bracketWidth());
      60              :                     }
      61              :                 }
      62              :             }
      63              :         }
      64              :     }
      65              : 
      66        14495 :     for (Bracket* b : bl) {
      67          124 :         delete b;
      68              :     }
      69              : 
      70        14371 :     double totalBracketWidth = 0.0;
      71              : 
      72        14371 :     if (!system->brackets().empty()) {
      73         5673 :         for (double w : bracketWidth) {
      74         3384 :             totalBracketWidth += w;
      75              :         }
      76              :     }
      77              : 
      78        14371 :     return totalBracketWidth;
      79        14371 : }
      80              : 
      81         3537 : Bracket* SystemHeaderLayout::createBracket(System* system, LayoutContext& ctx, BracketItem* bi, size_t column, staff_idx_t staffIdx,
      82              :                                            std::vector<Bracket*>& bl, Measure* measure)
      83              : {
      84         3537 :     if (!measure) {
      85            0 :         return nullptr;
      86              :     }
      87              : 
      88         3537 :     size_t nstaves = system->staves().size();
      89         3537 :     staff_idx_t firstStaff = staffIdx;
      90         3537 :     staff_idx_t lastStaff = staffIdx + bi->bracketSpan() - 1;
      91         3537 :     if (lastStaff >= nstaves) {
      92          665 :         lastStaff = nstaves - 1;
      93              :     }
      94              : 
      95         3811 :     for (; firstStaff <= lastStaff; ++firstStaff) {
      96         3682 :         if (system->staff(firstStaff)->show()) {
      97         3408 :             break;
      98              :         }
      99              :     }
     100         3846 :     for (; lastStaff >= firstStaff; --lastStaff) {
     101         3717 :         if (system->staff(lastStaff)->show()) {
     102         3408 :             break;
     103              :         }
     104              :     }
     105         3537 :     size_t span = lastStaff - firstStaff + 1;
     106              :     //
     107              :     // do not show bracket if it only spans one
     108              :     // system due to some invisible staves
     109              :     //
     110         3537 :     if (span > 1
     111          746 :         || (bi->bracketSpan() == span)
     112          721 :         || (span == 1 && ctx.conf().styleB(Sid::alwaysShowBracketsWhenEmptyStavesAreHidden)
     113            0 :             && bi->bracketType() != BracketType::SQUARE)
     114         4283 :         || (span == 1 && ctx.conf().styleB(Sid::alwaysShowSquareBracketsWhenEmptyStavesAreHidden)
     115            0 :             && bi->bracketType() == BracketType::SQUARE)) {
     116              :         //
     117              :         // this bracket is visible
     118              :         //
     119         2816 :         Bracket* b = 0;
     120         2816 :         track_idx_t track = staffIdx * VOICES;
     121         2872 :         for (size_t k = 0; k < bl.size(); ++k) {
     122         3051 :             if (bl[k]->track() == track && bl[k]->column() == column && bl[k]->bracketType() == bi->bracketType()
     123         3051 :                 && bl[k]->measure() == measure) {
     124         1481 :                 b = muse::takeAt(bl, k);
     125         1481 :                 break;
     126              :             }
     127              :         }
     128         2816 :         if (b == 0) {
     129         1335 :             b = Factory::createBracket(ctx.mutDom().dummyParent());
     130         1335 :             b->setBracketItem(bi);
     131         1335 :             b->setGenerated(true);
     132         1335 :             b->setTrack(track);
     133         1335 :             b->setMeasure(measure);
     134              :         }
     135         2816 :         system->add(b);
     136              : 
     137         2816 :         if (bi->selected()) {
     138            0 :             bool needSelect = true;
     139              : 
     140            0 :             std::vector<EngravingItem*> brackets = ctx.selection().elements(ElementType::BRACKET);
     141            0 :             for (const EngravingItem* element : brackets) {
     142            0 :                 if (toBracket(element)->bracketItem() == bi) {
     143            0 :                     needSelect = false;
     144            0 :                     break;
     145              :                 }
     146              :             }
     147              : 
     148            0 :             if (needSelect) {
     149            0 :                 ctx.select(b, SelectType::ADD);
     150              :             }
     151            0 :         }
     152              : 
     153         2816 :         b->setStaffSpan(firstStaff, lastStaff);
     154              : 
     155              :         InstrumentLabelVisibility visibility
     156         2816 :             = resolveInstrumentLabelVisibility(ctx.dom().staff(firstStaff), measure->tick(), ctx, ctx.state().firstSystem());
     157         2816 :         b->setLabelType(resolveInstrumentNameType(visibility));
     158              : 
     159         2816 :         return b;
     160              :     }
     161              : 
     162          721 :     return nullptr;
     163              : }
     164              : 
     165           99 : void SystemHeaderLayout::addBrackets(System* system, Measure* measure, LayoutContext& ctx)
     166              : {
     167           99 :     if (system->staves().empty()) {                 // ignore vbox
     168            0 :         return;
     169              :     }
     170              : 
     171           99 :     size_t nstaves = system->staves().size();
     172              : 
     173              :     //---------------------------------------------------
     174              :     //  find x position of staves
     175              :     //    create brackets
     176              :     //---------------------------------------------------
     177              : 
     178           99 :     size_t columns = system->getBracketsColumnsCount();
     179              : 
     180           99 :     std::vector<Bracket*> bl;
     181           99 :     bl.swap(system->brackets());
     182              : 
     183          245 :     for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
     184          245 :         for (size_t i = 0; i < columns; ++i) {
     185          177 :             for (BracketItem* bi : ctx.dom().brackets(staffIdx)) {
     186           78 :                 if (bi->column() != i || bi->bracketType() == BracketType::NO_BRACKET) {
     187           43 :                     continue;
     188              :                 }
     189           35 :                 createBracket(system, ctx, bi, i, staffIdx, bl, measure);
     190              :             }
     191              :         }
     192          146 :         if (!system->staff(staffIdx)->show()) {
     193            0 :             continue;
     194              :         }
     195              :     }
     196              : 
     197              :     //---------------------------------------------------
     198              :     //  layout brackets
     199              :     //---------------------------------------------------
     200           99 :     layoutBracketsVertical(system, ctx);
     201              : 
     202           99 :     setBracketsXPosition(system, measure->x());
     203              : 
     204           99 :     muse::join(system->brackets(), bl);
     205           99 : }
     206              : 
     207        14371 : double SystemHeaderLayout::totalBracketOffset(LayoutContext& ctx)
     208              : {
     209        14371 :     if (ctx.state().totalBracketsWidth() >= 0) {
     210        10411 :         return ctx.state().totalBracketsWidth();
     211              :     }
     212              : 
     213         3960 :     size_t columns = 0;
     214        10222 :     for (const Staff* staff : ctx.dom().staves()) {
     215         7676 :         for (const BracketItem* bi : ctx.dom().brackets(staff->idx())) {
     216         1414 :             columns = std::max(columns, bi->column() + 1);
     217              :         }
     218              :     }
     219              : 
     220         3960 :     size_t nstaves = ctx.dom().nstaves();
     221         3960 :     std::vector < double > bracketWidth(nstaves, 0.0);
     222        10222 :     for (staff_idx_t staffIdx = 0; staffIdx < nstaves; ++staffIdx) {
     223         7676 :         for (BracketItem* bi : ctx.dom().brackets(staffIdx)) {
     224         1414 :             if (bi->bracketType() == BracketType::NO_BRACKET || !bi->visible() || bi->bracketType() == BracketType::GROUP) {
     225          540 :                 continue;
     226              :             }
     227              : 
     228              :             //! This logic is partially copied from System::createBracket.
     229              :             //! Of course, we don't need to worry about invisible staves,
     230              :             //! but we do need to worry about brackets that span past the
     231              :             //! last staff.
     232          874 :             staff_idx_t firstStaff = staffIdx;
     233          874 :             staff_idx_t lastStaff = staffIdx + bi->bracketSpan() - 1;
     234          874 :             if (lastStaff >= nstaves) {
     235          124 :                 lastStaff = nstaves - 1;
     236              :             }
     237              : 
     238          933 :             for (; firstStaff <= lastStaff; ++firstStaff) {
     239          907 :                 if (ctx.dom().staff(firstStaff)->show()) {
     240          848 :                     break;
     241              :                 }
     242              :             }
     243          923 :             for (; lastStaff >= firstStaff; --lastStaff) {
     244          897 :                 if (ctx.dom().staff(lastStaff)->show()) {
     245          848 :                     break;
     246              :                 }
     247              :             }
     248              : 
     249          874 :             size_t span = lastStaff - firstStaff + 1;
     250          874 :             if (span > 1
     251          149 :                 || (bi->bracketSpan() == span)
     252         1023 :                 || (span == 1 && ctx.conf().styleB(Sid::alwaysShowBracketsWhenEmptyStavesAreHidden))) {
     253          734 :                 Bracket* dummyBr = Factory::createBracket(ctx.mutDom().dummyParent(), /*isAccessibleEnabled=*/ false);
     254          734 :                 dummyBr->setBracketItem(bi);
     255          734 :                 dummyBr->setStaffSpan(firstStaff, lastStaff);
     256          734 :                 dummyBr->mutldata()->bracketHeight.set_value(3.5 * dummyBr->spatium() * 2); // default
     257          734 :                 TLayout::layoutBracket(dummyBr, dummyBr->mutldata(), ctx.conf());
     258         2393 :                 for (staff_idx_t stfIdx = firstStaff; stfIdx <= lastStaff; ++stfIdx) {
     259         1659 :                     bracketWidth[stfIdx] += dummyBr->ldata()->bracketWidth();
     260              :                 }
     261          734 :                 delete dummyBr;
     262              :             }
     263              :         }
     264              :     }
     265              : 
     266         3960 :     double totalBracketsWidth = 0.0;
     267        10222 :     for (double w : bracketWidth) {
     268         6262 :         totalBracketsWidth = std::max(totalBracketsWidth, w);
     269              :     }
     270         3960 :     ctx.mutState().setTotalBracketsWidth(totalBracketsWidth);
     271              : 
     272         3960 :     return totalBracketsWidth;
     273         3960 : }
     274              : 
     275        14458 : void SystemHeaderLayout::setBracketsXPosition(System* system, double xPosition)
     276              : {
     277        14458 :     std::vector<Bracket*> brackets = system->brackets();
     278        17247 :     for (Bracket* b1 : brackets) {
     279         2789 :         BracketType bracketType = b1->bracketType();
     280              :         // For brackets that are drawn, we must correct for half line width
     281         2789 :         double lineWidthCorrection = 0.0;
     282         2789 :         if (bracketType == BracketType::NORMAL || bracketType == BracketType::LINE) {
     283          865 :             lineWidthCorrection = system->style().styleAbsolute(Sid::bracketWidth) / 2;
     284              :         }
     285              :         // Compute offset cause by other stacked brackets
     286         2789 :         double xOffset = 0;
     287         7868 :         for (const Bracket* b2 : brackets) {
     288         5079 :             if (!b2->bracketItem()->visible() || b2->bracketType() == BracketType::GROUP) {
     289            0 :                 continue;
     290              :             }
     291         5079 :             if (b1->column() > b2->column() && b1->intersects(b2)) {
     292          259 :                 xOffset += b2->ldata()->bracketWidth();
     293              :             }
     294              :         }
     295              :         // Set position
     296         2789 :         double x = xPosition - xOffset - b1->ldata()->bracketWidth() + lineWidthCorrection;
     297         2789 :         b1->mutldata()->setPosX(x);
     298              :     }
     299        14458 : }
     300              : 
     301        20468 : void SystemHeaderLayout::layoutBracketsVertical(System* system, LayoutContext& ctx)
     302              : {
     303        24389 :     for (Bracket* b : system->brackets()) {
     304         3921 :         int staffIdx1 = static_cast<int>(b->firstStaff());
     305         3921 :         int staffIdx2 = static_cast<int>(b->lastStaff());
     306         3921 :         double sy = 0;                           // assume bracket not visible
     307         3921 :         double ey = 0;
     308              :         // if start staff not visible, try next staff
     309         3921 :         while (staffIdx1 <= staffIdx2 && !system->staves().at(staffIdx1)->show()) {
     310            0 :             ++staffIdx1;
     311              :         }
     312              :         // if end staff not visible, try prev staff
     313         3921 :         while (staffIdx1 <= staffIdx2 && !system->staves().at(staffIdx2)->show()) {
     314            0 :             --staffIdx2;
     315              :         }
     316              :         // if the score doesn't have "alwaysShowBracketsWhenEmptyStavesAreHidden" as true,
     317              :         // the bracket will be shown IF:
     318              :         // it spans at least 2 visible staves (staffIdx1 < staffIdx2) OR
     319              :         // it spans just one visible staff (staffIdx1 == staffIdx2) but it is required to do so
     320              :         // (the second case happens at least when the bracket is initially dropped)
     321         3921 :         bool notHidden = ctx.conf().styleB(Sid::alwaysShowBracketsWhenEmptyStavesAreHidden)
     322         3921 :                          ? (staffIdx1 <= staffIdx2) : (staffIdx1 < staffIdx2) || (b->span() == 1 && staffIdx1 == staffIdx2);
     323         3921 :         if (notHidden) {                        // set vert. pos. and height to visible spanned staves
     324         3921 :             sy = system->staves().at(staffIdx1)->bbox().top();
     325         3921 :             ey = system->staves().at(staffIdx2)->bbox().bottom();
     326              :         }
     327              : 
     328         3921 :         Bracket::LayoutData* bldata = b->mutldata();
     329         3921 :         bldata->setPosY(sy);
     330         3921 :         bldata->bracketHeight = ey - sy;
     331         3921 :         TLayout::layoutBracket(b, bldata, ctx.conf());
     332              :     }
     333        20468 : }
     334              : 
     335        49099 : bool SystemHeaderLayout::stackLabelsVertically(System* system)
     336              : {
     337        49099 :     const MStyle& style = system->style();
     338              :     // Ignore long/short overrides
     339        49099 :     bool longName = system->ldata()->useLongNames();
     340              :     InstrumentNamesAlign align
     341        49099 :         = style.styleV(longName ? Sid::instrumentNamesAlignLong : Sid::instrumentNamesAlignShort).value<InstrumentNamesAlign>();
     342              :     return align == InstrumentNamesAlign::CENTER_CENTER
     343        49099 :            || (align == InstrumentNamesAlign::RIGHT_RIGHT && style.styleB(Sid::instrumentNamesStackVertically));
     344              : }
     345              : 
     346        14371 : void SystemHeaderLayout::computeInstrumentNameOffset(System* system, LayoutContext& ctx)
     347              : {
     348              :     // Get standard instrument name distance
     349        14371 :     double instrumentNameOffset = ctx.conf().styleAbsolute(Sid::instrumentNameOffset);
     350              :     // Now scale it depending on the text size (which also may not follow staff scaling)
     351        14371 :     double textSizeScaling = 1.0;
     352        14371 :     double actualSize = 0.0;
     353        14371 :     double defaultSize = 0.0;
     354        14371 :     bool followStaffSize = true;
     355        14371 :     if (ctx.state().startWithLongNames()) {
     356        14371 :         actualSize = ctx.conf().styleD(Sid::longInstrumentFontSize);
     357        14371 :         defaultSize = DefaultStyle::defaultStyle().value(Sid::longInstrumentFontSize).toDouble();
     358        14371 :         followStaffSize = ctx.conf().styleB(Sid::longInstrumentFontSpatiumDependent);
     359              :     } else {
     360            0 :         actualSize = ctx.conf().styleD(Sid::shortInstrumentFontSize);
     361            0 :         defaultSize = DefaultStyle::defaultStyle().value(Sid::shortInstrumentFontSize).toDouble();
     362            0 :         followStaffSize = ctx.conf().styleB(Sid::shortInstrumentFontSpatiumDependent);
     363              :     }
     364        14371 :     textSizeScaling = actualSize / defaultSize;
     365        14371 :     if (!followStaffSize) {
     366            0 :         textSizeScaling *= DefaultStyle::defaultStyle().value(Sid::spatium).toDouble() / ctx.conf().styleD(Sid::spatium);
     367              :     }
     368        14371 :     textSizeScaling = std::max(textSizeScaling, 1.0);
     369        14371 :     instrumentNameOffset *= textSizeScaling;
     370              : 
     371        14371 :     system->mutldata()->setInstrumentNameOffset(instrumentNameOffset);
     372        14371 : }
     373              : 
     374        14371 : void SystemHeaderLayout::computeGroupBracketsWidths(System* system, LayoutContext& ctx)
     375              : {
     376        14371 :     System::LayoutData* ldata = system->mutldata();
     377        14371 :     ldata->clearGroupBracketsWidth();
     378              : 
     379        14371 :     std::vector<Bracket*> groupBrackets;
     380        17152 :     for (Bracket* bracket : system->brackets()) {
     381         2781 :         if (bracket->bracketType() == BracketType::GROUP) {
     382            0 :             groupBrackets.push_back(bracket);
     383              :         }
     384              :     }
     385              : 
     386        14371 :     if (groupBrackets.empty()) {
     387        14371 :         return;
     388              :     }
     389              : 
     390            0 :     std::sort(groupBrackets.begin(), groupBrackets.end(), [](Bracket* b1, Bracket* b2) { return b1->column() < b2->column(); });
     391              : 
     392            0 :     bool hangIntoMargin = ctx.conf().styleB(Sid::groupBracketHangTextIntoMargin)
     393            0 :                           && ctx.conf().styleV(Sid::groupBracketTextOrientation).value<Orientation>() == Orientation::VERTICAL;
     394            0 :     double bracketDistToNames = ctx.conf().styleAbsolute(Sid::groupBracketDistanceToNames);
     395              : 
     396            0 :     for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
     397            0 :         Bracket* firstBracketOnThisStaff = nullptr;
     398            0 :         for (Bracket* b : groupBrackets) {
     399            0 :             if (b->contains(staffIdx)) {
     400            0 :                 firstBracketOnThisStaff = b;
     401            0 :                 break;
     402              :             }
     403              :         }
     404              : 
     405            0 :         if (!firstBracketOnThisStaff) {
     406            0 :             continue;
     407              :         }
     408              : 
     409            0 :         std::vector<Bracket*> stack;
     410            0 :         double bracketWidth = 0.0;
     411            0 :         computeStackedBracketsWidth(firstBracketOnThisStaff, groupBrackets, bracketWidth, stack);
     412            0 :         if (hangIntoMargin) {
     413            0 :             bracketWidth += stack.back()->ldata()->bbox().left();
     414              :         }
     415              : 
     416            0 :         ldata->setGroupBracketsWidthAtStaffIdx(staffIdx, bracketWidth + bracketDistToNames);
     417            0 :     }
     418              : 
     419            0 :     if (!ctx.conf().styleB(Sid::instrumentNamesAlignIncludeGroupBrackets)) {
     420            0 :         double maxGroupBracketWidth = 0.0;
     421            0 :         for (const auto& pair : ldata->groupBracketsWidth()) {
     422            0 :             maxGroupBracketWidth = std::max(maxGroupBracketWidth, pair.second);
     423              :         }
     424            0 :         for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
     425            0 :             ldata->setGroupBracketsWidthAtStaffIdx(staffIdx, maxGroupBracketWidth);
     426              :         }
     427              :     }
     428        14371 : }
     429              : 
     430            0 : void SystemHeaderLayout::computeStackedBracketsWidth(Bracket* first, const std::vector<Bracket*>& allGroupBracketsOrderedByColumn,
     431              :                                                      double& width, std::vector<Bracket*>& stack)
     432              : {
     433            0 :     stack.push_back(first);
     434              : 
     435            0 :     width += first->ldata()->bbox().width();
     436            0 :     double bracketDist = first->style().styleAbsolute(Sid::groupBracketDistanceToGroupBracket);
     437              : 
     438            0 :     for (Bracket* b2 : allGroupBracketsOrderedByColumn) {
     439            0 :         if (muse::contains(stack, b2)) {
     440            0 :             continue;
     441              :         }
     442            0 :         if (b2->column() > first->column() && b2->intersects(first)) {
     443            0 :             stack.push_back(b2);
     444            0 :             computeStackedBracketsWidth(b2, allGroupBracketsOrderedByColumn, width, stack);
     445            0 :             width += bracketDist;
     446              :         }
     447              :     }
     448            0 : }
     449              : 
     450        15791 : double SystemHeaderLayout::nameWidthIncludingGroupBrackets(InstrumentName* name, System* system)
     451              : {
     452        15791 :     const System::LayoutData* ldata = system->ldata();
     453        15791 :     double groupBracketsWidth = ldata->groupBracketsWidthAtStaffIdx(name->effectiveStaffIdx());
     454        15791 :     double result = name->width();
     455        15791 :     if (!muse::RealIsNull(groupBracketsWidth)) {
     456            0 :         result += groupBracketsWidth;
     457              :     }
     458        15791 :     return result;
     459              : }
     460              : 
     461        14371 : void SystemHeaderLayout::computeInstrumentNamesWidth(System* system, LayoutContext& ctx)
     462              : {
     463        14371 :     computeGroupBracketsWidths(system, ctx);
     464              : 
     465        14371 :     System::LayoutData* ldata = system->mutldata();
     466        14371 :     ldata->setFirstColumnWidth(0.0);
     467        14371 :     ldata->setSecondColumnWidth(0.0);
     468        14371 :     ldata->setTotalNamesWidth(0.0);
     469              : 
     470        14371 :     std::unordered_set<Part*> partsWithIndividualStaffNames;
     471        14371 :     std::unordered_set<Part*> partsWithGroupNames;
     472              : 
     473        14371 :     std::vector<InstrumentName*> groupNames;
     474        14371 :     std::vector<InstrumentName*> instrumentNames;
     475        14371 :     std::vector<InstrumentName*> sharedStaveNames;
     476              : 
     477        36159 :     for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
     478        21788 :         const SysStaff* sysStaff = system->staff(staffIdx);
     479        21788 :         const Staff* staff = ctx.dom().staff(staffIdx);
     480              : 
     481        21788 :         if (InstrumentName* groupName = sysStaff->name(InstrumentNameRole::GROUP)) {
     482          134 :             if (groupName->instrumentNameType() == InstrumentNameType::LONG && groupName->effectiveStaffIdx() == muse::nidx) {
     483              :                 // NOTE: We can only do this for long-name systems because they don't need to have
     484              :                 // the left barline aligned with the other systems across the page.
     485            0 :                 groupName->mutldata()->setIsSkipDraw(true);
     486            0 :                 continue;
     487              :             }
     488          134 :             groupName->mutldata()->setIsSkipDraw(false);
     489              : 
     490          134 :             groupNames.push_back(groupName);
     491          134 :             groupName->mutldata()->setColumn(1);
     492          424 :             for (staff_idx_t groupIdx = groupName->staffIdx(); groupIdx < groupName->ldata()->endIdxOfGroup(); ++groupIdx) {
     493          290 :                 partsWithGroupNames.insert(ctx.dom().staff(groupIdx)->part());
     494              :             }
     495          134 :             ldata->setSecondColumnWidth(std::max(ldata->secondColumnWidth(), nameWidthIncludingGroupBrackets(groupName, system)));
     496          134 :             ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), nameWidthIncludingGroupBrackets(groupName, system)));
     497              :         }
     498              : 
     499        21788 :         if (!staff->show()) {
     500              :             // We know that the staff is hidden in the entire score so safe to skip
     501          287 :             continue;
     502              :         }
     503              : 
     504        21501 :         if (InstrumentName* name = sysStaff->name(InstrumentNameRole::STAFF)) {
     505          140 :             if (name->instrumentNameType() == InstrumentNameType::LONG && !sysStaff->show()) {
     506              :                 // NOTE: We can only do this for long-name systems because they don't need to have
     507              :                 // the left barline aligned with the other systems across the page.
     508            0 :                 name->mutldata()->setIsSkipDraw(true);
     509            0 :                 continue;
     510              :             }
     511          140 :             name->mutldata()->setIsSkipDraw(false);
     512              : 
     513          140 :             name->mutldata()->setColumn(0);
     514          140 :             partsWithIndividualStaffNames.insert(ctx.dom().staff(staffIdx)->part());
     515          140 :             ldata->setFirstColumnWidth(std::max(ldata->firstColumnWidth(), name->width()));
     516          140 :             ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), nameWidthIncludingGroupBrackets(name, system)));
     517              :         }
     518              :     }
     519              : 
     520        36159 :     for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
     521        21788 :         const SysStaff* sysStaff = system->staff(staffIdx);
     522        21788 :         const Staff* staff = ctx.dom().staff(staffIdx);
     523        21788 :         Part* part = staff->part();
     524              : 
     525        21788 :         if (!part->show() || part->visibleStavesCount() == 0) {
     526              :             // We know that the part is hidden in the entire score so safe to skip
     527        14092 :             continue;
     528              :         }
     529              : 
     530        21509 :         InstrumentName* instrName = sysStaff->name(InstrumentNameRole::PART);
     531        21509 :         if (!instrName) {
     532        13808 :             instrName = sysStaff->name(InstrumentNameRole::SHARED_STAFF);
     533              :         }
     534        21509 :         if (!instrName) {
     535        13808 :             continue;
     536              :         }
     537              : 
     538         7701 :         if (instrName->instrumentNameType() == InstrumentNameType::LONG && instrName->effectiveStaffIdx() == muse::nidx) {
     539              :             // NOTE: We can only do this for long-name systems because they don't need to have
     540              :             // the left barline aligned with the other systems across the page.
     541            5 :             instrName->mutldata()->setIsSkipDraw(true);
     542            5 :             continue;
     543              :         }
     544         7696 :         instrName->mutldata()->setIsSkipDraw(false);
     545              : 
     546         7696 :         if (instrName->instrumentNameRole() == InstrumentNameRole::PART) {
     547         7696 :             instrumentNames.push_back(instrName);
     548              :         } else {
     549            0 :             sharedStaveNames.push_back(instrName);
     550              :         }
     551              : 
     552         7696 :         ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), nameWidthIncludingGroupBrackets(instrName, system)));
     553              : 
     554         7696 :         if (partsWithGroupNames.count(part)) {
     555          156 :             instrName->mutldata()->setColumn(0);
     556          156 :             ldata->setFirstColumnWidth(std::max(ldata->firstColumnWidth(), instrName->width()));
     557              :         } else {
     558         7540 :             instrName->mutldata()->setColumn(1);
     559         7540 :             ldata->setSecondColumnWidth(std::max(ldata->secondColumnWidth(), nameWidthIncludingGroupBrackets(instrName, system)));
     560              :         }
     561              :     }
     562              : 
     563          147 :     auto sumWidth = [&](InstrumentName* outerName, InstrumentName* innerName) {
     564          147 :         AlignH staffNameAlign = innerName->position();
     565          147 :         if (staffNameAlign == AlignH::LEFT || staffNameAlign == AlignH::JUSTIFY) {
     566            0 :             return ldata->firstColumnWidth() + nameWidthIncludingGroupBrackets(outerName, system) + ldata->instrumentNameOffset();
     567          147 :         } else if (staffNameAlign == AlignH::HCENTER) {
     568            0 :             double sumWidth = innerName->width() + nameWidthIncludingGroupBrackets(outerName, system) + ldata->instrumentNameOffset();
     569            0 :             double move = 0.5 * (ldata->firstColumnWidth() - innerName->width());
     570            0 :             return sumWidth + move;
     571              :         } else {
     572          147 :             return innerName->width() + ldata->instrumentNameOffset() + nameWidthIncludingGroupBrackets(outerName, system);
     573              :         }
     574        14371 :     };
     575              : 
     576        14371 :     for (InstrumentName* sharedStaffName : sharedStaveNames) {
     577            0 :         if (sharedStaffName->ldata()->column() == 0) {
     578            0 :             continue;
     579              :         }
     580            0 :         Part* part = ctx.dom().staff(sharedStaffName->staffIdx())->part();
     581            0 :         for (staff_idx_t idx : part->staveIdxList()) {
     582            0 :             if (InstrumentName* n = system->staff(idx)->name(InstrumentNameRole::STAFF); n && !n->ldata()->isSkipDraw()) {
     583            0 :                 ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), sumWidth(sharedStaffName, n)));
     584              :             }
     585            0 :         }
     586              :     }
     587              : 
     588        14371 :     if (stackLabelsVertically(system)) {
     589            0 :         return;
     590              :     }
     591              : 
     592        14505 :     for (InstrumentName* groupName : groupNames) {
     593          134 :         staff_idx_t startStaff = groupName->staffIdx();
     594          134 :         staff_idx_t endStaff = groupName->ldata()->endIdxOfGroup();
     595          340 :         if (groupName->instrumentNameType() == InstrumentNameType::LONG
     596          134 :             && system->visiblePartsOfGroup(startStaff, endStaff).size() % 2 == 0) {
     597           72 :             continue;
     598              :         }
     599          196 :         for (staff_idx_t idx = startStaff; idx < endStaff; ++idx) {
     600          134 :             if (InstrumentName* n = system->staff(idx)->name(InstrumentNameRole::STAFF); n && !n->ldata()->isSkipDraw()) {
     601            0 :                 ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), sumWidth(groupName, n)));
     602              :             }
     603          134 :             if (InstrumentName* n = system->staff(idx)->name(InstrumentNameRole::PART); n && !n->ldata()->isSkipDraw()) {
     604           72 :                 ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), sumWidth(groupName, n)));
     605              :             }
     606              :         }
     607              :     }
     608              : 
     609        22067 :     for (InstrumentName* instrName : instrumentNames) {
     610         7696 :         if (instrName->ldata()->column() == 0) {
     611          156 :             continue;
     612              :         }
     613         7540 :         Part* part = ctx.dom().staff(instrName->staffIdx())->part();
     614         7540 :         if (instrName->instrumentNameType() == InstrumentNameType::LONG && system->visibleStavesOfPart(part).size() % 2 == 0) {
     615          558 :             continue;
     616              :         }
     617        14257 :         for (staff_idx_t idx : part->staveIdxList()) {
     618         7275 :             if (InstrumentName* n = system->staff(idx)->name(InstrumentNameRole::STAFF); n && !n->ldata()->isSkipDraw()) {
     619           75 :                 ldata->setTotalNamesWidth(std::max(ldata->totalNamesWidth(), sumWidth(instrName, n)));
     620              :             }
     621         6982 :         }
     622              :     }
     623        14371 : }
     624              : 
     625        20369 : void SystemHeaderLayout::setInstrumentNamesVerticalPos(System* system, LayoutContext& ctx)
     626              : {
     627        20369 :     std::set<const Part*> partsWithIndividualStaffNames;
     628              : 
     629        51336 :     for (staff_idx_t staffIdx = 0; staffIdx < ctx.dom().nstaves(); ++staffIdx) {
     630        30967 :         SysStaff* s = system->staff(staffIdx);
     631        92901 :         for (InstrumentNameRole role : { InstrumentNameRole::STAFF, InstrumentNameRole::SHARED_STAFF }) {
     632        61934 :             if (InstrumentName* individualName = s->name(role)) {
     633          186 :                 const RectF& staffBBox = s->bbox();
     634          186 :                 const RectF& nameBBox = individualName->ldata()->bbox();
     635          186 :                 individualName->mutldata()->setPosY(0.5 * (staffBBox.top() + staffBBox.bottom() - nameBBox.bottom() - nameBBox.top()));
     636          186 :                 partsWithIndividualStaffNames.insert(ctx.dom().staff(staffIdx)->part());
     637              :             }
     638              :         }
     639              :     }
     640              : 
     641        20369 :     bool stackVertically = stackLabelsVertically(system);
     642              : 
     643        20369 :     staff_idx_t staffIdx = 0;
     644        47727 :     for (const Part* p : ctx.dom().parts()) {
     645        27358 :         size_t nstaves = p->nstaves();
     646              : 
     647        27358 :         SysStaff* s = system->staff(staffIdx);
     648              : 
     649        27358 :         InstrumentName* t = s->name(InstrumentNameRole::PART);
     650        27358 :         if (!t || t->effectiveStaffIdx() == muse::nidx) {
     651        15910 :             staffIdx += nstaves;
     652        27220 :             continue;
     653              :         }
     654              : 
     655        11448 :         const RectF& bbox = t->ldata()->bbox();
     656        11448 :         double yCenter = 0.5 * (bbox.bottom() + bbox.top());
     657              : 
     658        11448 :         std::vector<staff_idx_t> visibleStavesOfPart = system->visibleStavesOfPart(p);
     659        11448 :         size_t visibleStavesCount = visibleStavesOfPart.size();
     660        11448 :         DO_ASSERT(visibleStavesCount > 0);
     661              : 
     662        11448 :         double y1 = 0;
     663        11448 :         double y2 = 0;
     664              : 
     665        11448 :         if (!muse::contains(partsWithIndividualStaffNames, p)) {
     666        11286 :             SysStaff* topSt = system->staff(visibleStavesOfPart.front());
     667        11286 :             SysStaff* bottomSt = system->staff(visibleStavesOfPart.back());
     668        11286 :             y1 = topSt->bbox().top();
     669        11286 :             y2 = bottomSt->bbox().bottom();
     670        11286 :             t->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
     671              : 
     672        11286 :             staffIdx += nstaves;
     673        11286 :             continue;
     674        11286 :         }
     675              : 
     676          162 :         if (visibleStavesCount % 2 == 0) {
     677           24 :             SysStaff* staffAboveMid = system->staff(visibleStavesOfPart[visibleStavesCount / 2 - 1]);
     678           24 :             SysStaff* staffBelowMid = system->staff(visibleStavesOfPart[visibleStavesCount / 2]);
     679           24 :             y1 = staffAboveMid->bbox().top();
     680           24 :             y2 = staffBelowMid->bbox().bottom();
     681           24 :             t->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
     682              : 
     683           24 :             staffIdx += nstaves;
     684           24 :             continue;
     685           24 :         }
     686              : 
     687          138 :         SysStaff* midStaff = system->staff(visibleStavesOfPart[visibleStavesCount / 2]);
     688          138 :         y1 = midStaff->bbox().top();
     689          138 :         y2 = midStaff->bbox().bottom();
     690          138 :         if (InstrumentName* staffName = midStaff->name(InstrumentNameRole::STAFF)) {
     691          138 :             if (stackVertically || t->ldata()->column() == 0) {
     692            0 :                 double lineSpacing = t->lineSpacing();
     693            0 :                 double instrNameBottom = t->ldata()->blocks.back().y();
     694            0 :                 double centerY = 0.5 * (midStaff->bbox().top() + midStaff->bbox().bottom());
     695            0 :                 t->mutldata()->setPosY(centerY - instrNameBottom - 0.25 * lineSpacing);
     696            0 :                 double staffNameTop = staffName->ldata()->blocks.front().y();
     697            0 :                 staffName->mutldata()->setPosY(centerY - staffNameTop + 0.75 * lineSpacing);
     698          138 :             } else if (staffName->ldata()->rows() == 1 && t->ldata()->rows() == 1) {
     699          138 :                 t->mutldata()->setPosY(staffName->y());
     700              :             } else {
     701            0 :                 t->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
     702              :             }
     703              :         }
     704              : 
     705          138 :         staffIdx += nstaves;
     706        11448 :     }
     707              : 
     708        51336 :     for (staff_idx_t idx = 0; idx < system->staves().size(); ++idx) {
     709        30967 :         InstrumentName* groupName = system->staff(idx)->name(InstrumentNameRole::GROUP);
     710        30967 :         if (!groupName || groupName->effectiveStaffIdx() == muse::nidx) {
     711        30967 :             continue;
     712              :         }
     713              : 
     714          201 :         const RectF& bbox = groupName->ldata()->bbox();
     715          201 :         double yCenter = 0.5 * (bbox.bottom() + bbox.top());
     716              : 
     717          201 :         std::vector<Part*> visibleParts = system->visiblePartsOfGroup(idx, groupName->ldata()->endIdxOfGroup());
     718          201 :         size_t visiblePartsCount = visibleParts.size();
     719          201 :         DO_ASSERT(visiblePartsCount > 0);
     720              : 
     721          201 :         double y1 = 0;
     722          201 :         double y2 = 0;
     723              : 
     724          201 :         if (visiblePartsCount % 2 == 0) {
     725          186 :             Part* partAboveMid = visibleParts[visiblePartsCount / 2 - 1];
     726          186 :             staff_idx_t staffIdxAboveMid = system->lastVisibleSysStaffOfPart(partAboveMid);
     727          186 :             DO_ASSERT(staffIdxAboveMid != muse::nidx);
     728          186 :             SysStaff* staffAboveMid = system->staff(staffIdxAboveMid);
     729              : 
     730          186 :             Part* partBelowMid = visibleParts[visiblePartsCount / 2];
     731          186 :             staff_idx_t staffIdxBelowMid = system->firstVisibleSysStaffOfPart(partBelowMid);
     732          186 :             DO_ASSERT(staffIdxBelowMid != muse::nidx);
     733          186 :             SysStaff* staffBelowMid = system->staff(staffIdxBelowMid);
     734              : 
     735          186 :             y1 = staffAboveMid->bbox().top();
     736          186 :             y2 = staffBelowMid->bbox().bottom();
     737          186 :             groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
     738              : 
     739          186 :             continue;
     740          186 :         }
     741              : 
     742           15 :         Part* midPart = visibleParts[visiblePartsCount / 2];
     743           15 :         InstrumentName* instrName = system->staff(*midPart->staveIdxList().begin())->name(InstrumentNameRole::PART);
     744           15 :         std::vector<staff_idx_t> visibleStaves = system->visibleStavesOfPart(midPart);
     745           15 :         size_t visibleStavesCount = visibleStaves.size();
     746              : 
     747           15 :         if (visibleStavesCount % 2) {
     748           15 :             SysStaff* midStaff = system->staff(visibleStaves[visibleStavesCount / 2]);
     749           15 :             y1 = midStaff->bbox().top();
     750           15 :             y2 = midStaff->bbox().bottom();
     751              :         } else {
     752            0 :             SysStaff* staffAboveMid = system->staff(visibleStaves[visibleStavesCount / 2 - 1]);
     753            0 :             SysStaff* staffBelowMid = system->staff(visibleStaves[visibleStavesCount / 2]);
     754            0 :             y1 = staffAboveMid->bbox().top();
     755            0 :             y2 = staffBelowMid->bbox().bottom();
     756              :         }
     757              : 
     758           15 :         if (instrName) {
     759            0 :             if (stackVertically) {
     760            0 :                 double lineSpacing = groupName->lineSpacing();
     761            0 :                 double groupNameBottom = groupName->ldata()->blocks.back().y();
     762            0 :                 if (visibleStavesCount % 2 && system->staff(visibleStaves[visibleStavesCount / 2])->name(InstrumentNameRole::STAFF)) {
     763            0 :                     groupName->mutldata()->setPosY(instrName->y() - groupNameBottom - lineSpacing);
     764              :                 } else {
     765            0 :                     double centerY = 0.5 * (y1 + y2);
     766            0 :                     groupName->mutldata()->setPosY(centerY - groupNameBottom - 0.25 * lineSpacing);
     767            0 :                     double instrNameTop = instrName->ldata()->blocks.front().y();
     768            0 :                     instrName->mutldata()->setPosY(centerY - instrNameTop + 0.75 * lineSpacing);
     769              :                 }
     770            0 :             } else if (instrName->ldata()->rows() == 1 && groupName->ldata()->rows() == 1) {
     771            0 :                 groupName->mutldata()->setPosY(instrName->y());
     772              :             } else {
     773            0 :                 groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
     774              :             }
     775              : 
     776            0 :             continue;
     777            0 :         }
     778              : 
     779           15 :         if (visibleStavesCount % 2 == 0) {
     780            0 :             groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
     781            0 :             continue;
     782              :         }
     783              : 
     784           15 :         SysStaff* midStaff = system->staff(visibleStaves[visibleStavesCount / 2]);
     785           15 :         InstrumentName* staffName = midStaff->name(InstrumentNameRole::STAFF);
     786           15 :         if (!staffName) {
     787           15 :             groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
     788           15 :             continue;
     789              :         }
     790              : 
     791            0 :         if (stackVertically) {
     792            0 :             double lineSpacing = groupName->lineSpacing();
     793            0 :             double groupNameBottom = groupName->ldata()->blocks.back().y();
     794            0 :             double centerY = 0.5 * (y1 + y2);
     795            0 :             groupName->mutldata()->setPosY(centerY - groupNameBottom - 0.25 * lineSpacing);
     796            0 :             double staffNameTop = staffName->ldata()->blocks.front().y();
     797            0 :             staffName->mutldata()->setPosY(centerY - staffNameTop + 0.75 * lineSpacing);
     798            0 :         } else if (staffName->ldata()->rows() == 1 && groupName->ldata()->rows() == 1) {
     799            0 :             groupName->mutldata()->setPosY(staffName->y());
     800              :         } else {
     801            0 :             groupName->mutldata()->setPosY(0.5 * (y1 + y2) - yCenter);
     802              :         }
     803          216 :     }
     804        20369 : }
     805              : 
     806        14359 : void SystemHeaderLayout::setInstrumentNamesHorizontalPos(System* system)
     807              : {
     808              :     //---------------------------------------------------
     809              :     //  layout instrument names x position
     810              :     //     at this point it is not clear which staves will
     811              :     //     be hidden, so layout all instrument names
     812              :     //---------------------------------------------------
     813              : 
     814        14359 :     System::LayoutData* ldata = system->mutldata();
     815        14359 :     double totalNamesWidth = ldata->totalNamesWidth();
     816        14359 :     double firstColumnWidth = ldata->firstColumnWidth();
     817              :     // Ignore long/short overrides
     818        28718 :     InstrumentNamesAlign align = system->style().styleV(
     819        28718 :         ldata->useLongNames() ? Sid::instrumentNamesAlignLong : Sid::instrumentNamesAlignShort).value<InstrumentNamesAlign>();
     820              : 
     821          278 :     auto placeFirstColumnName = [&](InstrumentName* name) {
     822          278 :         const RectF& bbox = name->ldata()->bbox();
     823          278 :         if (align == InstrumentNamesAlign::CENTER_CENTER) {
     824            0 :             double groupBracketsWidth = ldata->groupBracketsWidthAtStaffIdx(name->effectiveStaffIdx());
     825            0 :             name->mutldata()->setPosX(0.5 * totalNamesWidth - 0.5 * (bbox.right() + bbox.left()) + groupBracketsWidth);
     826              :         } else {
     827          278 :             switch (name->position()) {
     828            0 :             case AlignH::JUSTIFY:
     829              :             case AlignH::LEFT:
     830            0 :                 name->mutldata()->setPosX(totalNamesWidth - firstColumnWidth - bbox.left());
     831            0 :                 break;
     832            0 :             case AlignH::HCENTER:
     833            0 :                 name->mutldata()->setPosX(totalNamesWidth - 0.5 * firstColumnWidth - 0.5 * (bbox.right() + bbox.left()));
     834            0 :                 break;
     835          278 :             case AlignH::RIGHT:
     836          278 :                 name->mutldata()->setPosX(totalNamesWidth - bbox.right());
     837              :             }
     838              :         }
     839        14637 :     };
     840              : 
     841        36089 :     for (const SysStaff* s : system->staves()) {
     842        86920 :         for (InstrumentNameRole role : { InstrumentNameRole::STAFF,
     843              :                                          InstrumentNameRole::SHARED_STAFF,
     844       108650 :                                          InstrumentNameRole::PART }) {
     845        65190 :             InstrumentName* n = s->name(role);
     846        65190 :             if (n && n->ldata()->column() == 0 && n->effectiveStaffIdx() != muse::nidx) {
     847          278 :                 placeFirstColumnName(n);
     848              :             }
     849              :         }
     850              :     }
     851              : 
     852        14359 :     bool stackVertically = stackLabelsVertically(system);
     853              : 
     854         7647 :     auto placeSecondColumnName = [&](InstrumentName* name, staff_idx_t staffIdx) {
     855         7647 :         bool doStackVertically = stackVertically && name->instrumentNameRole() != InstrumentNameRole::SHARED_STAFF;
     856         7647 :         const RectF& bbox = name->ldata()->bbox();
     857         7647 :         double groupBracketsWidth = ldata->groupBracketsWidthAtStaffIdx(name->effectiveStaffIdx());
     858              : 
     859         7647 :         if (align == InstrumentNamesAlign::LEFT_RIGHT) {
     860            0 :             name->mutldata()->setPosX(groupBracketsWidth - bbox.left());
     861         7637 :             return;
     862              :         }
     863              : 
     864         7647 :         if (align == InstrumentNamesAlign::CENTER_CENTER) {
     865            0 :             name->mutldata()->setPosX(0.5 * totalNamesWidth - 0.5 * (bbox.right() + bbox.left()) + 0.5 * groupBracketsWidth);
     866            0 :             return;
     867              :         }
     868              : 
     869         7647 :         if (align == InstrumentNamesAlign::CENTER_RIGHT) {
     870            0 :             name->mutldata()->setPosX(0.5 * ldata->secondColumnWidth() - 0.5 * (bbox.right() + bbox.left()) + 0.5 * groupBracketsWidth);
     871            0 :             return;
     872              :         }
     873              : 
     874         7647 :         if (doStackVertically) {
     875            0 :             name->mutldata()->setPosX(totalNamesWidth - bbox.right());
     876            0 :             return;
     877              :         }
     878              : 
     879         7647 :         if (name->instrumentNameRole() == InstrumentNameRole::SHARED_STAFF) {
     880            0 :             if (InstrumentName* staffName = system->staff(name->staffIdx())->name(InstrumentNameRole::STAFF)) {
     881            0 :                 name->mutldata()->setPosX(
     882            0 :                     staffName->x() + staffName->ldata()->bbox().left() - bbox.right() - ldata->instrumentNameOffset());
     883              :             } else {
     884            0 :                 name->mutldata()->setPosX(totalNamesWidth - bbox.right());
     885              :             }
     886              : 
     887            0 :             return;
     888              :         }
     889              : 
     890         7647 :         if (name->instrumentNameRole() == InstrumentNameRole::PART) {
     891         7513 :             const Part* p = system->score()->staff(staffIdx)->part();
     892         7513 :             std::vector<staff_idx_t> visibleStavesForPart = system->visibleStavesOfPart(p);
     893         7513 :             size_t visibleStaveCount = visibleStavesForPart.size();
     894         7513 :             if (visibleStaveCount % 2 == 0) {
     895          773 :                 name->mutldata()->setPosX(totalNamesWidth - bbox.right());
     896          773 :                 return;
     897              :             }
     898              : 
     899         6740 :             staff_idx_t centerStaff = visibleStavesForPart[visibleStaveCount / 2];
     900         6740 :             if (InstrumentName* staffName = system->staff(centerStaff)->name(InstrumentNameRole::STAFF)) {
     901          100 :                 name->mutldata()->setPosX(
     902           50 :                     staffName->x() + staffName->ldata()->bbox().left() - bbox.right() - ldata->instrumentNameOffset());
     903              :             } else {
     904         6690 :                 name->mutldata()->setPosX(totalNamesWidth - bbox.right());
     905              :             }
     906              : 
     907         6740 :             return;
     908         7513 :         }
     909              : 
     910          134 :         std::vector<Part*> visiblePartsOfGroup = system->visiblePartsOfGroup(staffIdx, name->ldata()->endIdxOfGroup());
     911          134 :         size_t visiblePartsCount = visiblePartsOfGroup.size();
     912          134 :         if (visiblePartsCount % 2 == 0) {
     913          124 :             name->mutldata()->setPosX(totalNamesWidth - bbox.right());
     914          124 :             return;
     915              :         }
     916              : 
     917           10 :         Part* centerPart = visiblePartsOfGroup[visiblePartsCount / 2];
     918           10 :         if (InstrumentName* instrName = system->staff(*centerPart->staveIdxList().begin())->name(InstrumentNameRole::PART)) {
     919            0 :             name->mutldata()->setPosX(
     920            0 :                 instrName->x() + instrName->ldata()->bbox().left() - bbox.right() - ldata->instrumentNameOffset());
     921            0 :             return;
     922              :         }
     923              : 
     924           10 :         std::vector<staff_idx_t> visibleStavesOfPart = system->visibleStavesOfPart(centerPart);
     925           10 :         size_t visibleStavesCount = visibleStavesOfPart.size();
     926           10 :         if (visibleStavesCount % 2 == 0) {
     927            0 :             name->mutldata()->setPosX(totalNamesWidth - bbox.right());
     928            0 :             return;
     929              :         }
     930              : 
     931           10 :         staff_idx_t centerStaff = visibleStavesOfPart[visibleStavesCount / 2];
     932           10 :         if (InstrumentName* staffName = system->staff(centerStaff)->name(InstrumentNameRole::STAFF)) {
     933            0 :             name->mutldata()->setPosX(
     934            0 :                 staffName->x() + staffName->ldata()->bbox().left() - bbox.right() - ldata->instrumentNameOffset());
     935              :         } else {
     936           10 :             name->mutldata()->setPosX(totalNamesWidth - bbox.right());
     937              :         }
     938          134 :     };
     939              : 
     940        36089 :     for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
     941        21730 :         const SysStaff* s = system->staff(staffIdx);
     942        21730 :         InstrumentName* instrName = s->name(InstrumentNameRole::PART);
     943        21730 :         if (!instrName) {
     944        14044 :             instrName = s->name(InstrumentNameRole::SHARED_STAFF);
     945              :         }
     946        21730 :         if (!instrName) {
     947        14044 :             continue;
     948              :         }
     949         7686 :         if (instrName && instrName->effectiveStaffIdx() != muse::nidx && instrName->ldata()->column() > 0) {
     950         7513 :             placeSecondColumnName(instrName, staffIdx);
     951              :         }
     952              :     }
     953              : 
     954        36089 :     for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
     955        21730 :         const SysStaff* s = system->staff(staffIdx);
     956        21730 :         InstrumentName* groupName = s->name(InstrumentNameRole::GROUP);
     957        21730 :         if (groupName && groupName->effectiveStaffIdx() != muse::nidx) {
     958          134 :             placeSecondColumnName(groupName, staffIdx);
     959              :         }
     960              :     }
     961        14359 : }
     962              : 
     963        14359 : void SystemHeaderLayout::setGroupBracketsHorizontalPos(System* system)
     964              : {
     965        14359 :     std::vector<Bracket*> groupBrackets;
     966        17113 :     for (Bracket* b : system->brackets()) {
     967         2754 :         if (b->bracketType() == BracketType::GROUP) {
     968            0 :             groupBrackets.push_back(b);
     969              :         }
     970              :     }
     971        14359 :     std::sort(groupBrackets.begin(), groupBrackets.end(), [](Bracket* b1, Bracket* b2) { return b1->column() > b2->column(); });
     972              : 
     973        14359 :     const MStyle& style = system->style();
     974        14359 :     bool intoMargin = style.styleB(Sid::groupBracketHangTextIntoMargin)
     975        14359 :                       && style.styleV(Sid::groupBracketTextOrientation).value<Orientation>() == Orientation::VERTICAL;
     976        14359 :     double bracketsDist = style.styleAbsolute(Sid::groupBracketDistanceToGroupBracket);
     977              : 
     978        14359 :     for (Bracket* b : groupBrackets) {
     979            0 :         b->mutldata()->setPosX(intoMargin ? 0.0 : -b->mutldata()->bbox().left());
     980            0 :         for (Bracket* bb : groupBrackets) {
     981            0 :             if (bb == b) {
     982            0 :                 break;
     983              :             }
     984            0 :             if (bb->column() > b->column() && bb->intersects(b)) {
     985            0 :                 b->mutldata()->setPosX(bb->x() + bb->ldata()->bbox().right() - b->ldata()->bbox().left() + bracketsDist);
     986              :             }
     987              :         }
     988              :     }
     989        14359 : }
     990              : 
     991        64975 : InstrumentName* SystemHeaderLayout::updateName(System* system, staff_idx_t staffIdx, LayoutContext& ctx, const String& name,
     992              :                                                InstrumentNameType type, InstrumentNameRole role)
     993              : {
     994        64975 :     SysStaff* sysStaff = system->staff(staffIdx);
     995        64975 :     InstrumentName* iname = sysStaff->name(role);
     996              : 
     997        64975 :     if (name.empty()) {
     998        57000 :         if (iname) {
     999            0 :             ctx.mutDom().removeElement(iname);
    1000              :         }
    1001        57000 :         return nullptr;
    1002              :     }
    1003              : 
    1004         7975 :     if (!iname) {
    1005         3771 :         iname = new InstrumentName(system);
    1006         3771 :         iname->setGenerated(true);
    1007         3771 :         iname->setSysStaff(sysStaff);
    1008         3771 :         iname->setTrack(staffIdx * VOICES);
    1009         3771 :         iname->setInstrumentNameRole(role);
    1010         3771 :         ctx.mutDom().addElement(iname);
    1011              :     }
    1012              : 
    1013         7975 :     iname->setInstrumentNameType(type);
    1014         7975 :     iname->setAlign(Align(iname->align().horizontal, AlignV::BASELINE));
    1015         7975 :     iname->setXmlText(name);
    1016              : 
    1017         7975 :     iname->mutldata()->setColumn(0); // Reset here, will be computed later
    1018         7975 :     TLayout::layoutInstrumentName(iname, iname->mutldata());
    1019              : 
    1020         7975 :     return iname;
    1021              : }
    1022              : 
    1023         8614 : String SystemHeaderLayout::formattedInstrumentName(System* system, Part* part, const Fraction& tick, bool longNames)
    1024              : {
    1025         8614 :     Instrument* instr = part->instrument(tick);
    1026         8614 :     const InstrumentLabel& label = instr->instrumentLabel();
    1027              : 
    1028         8614 :     const MStyle& style = system->style();
    1029         8614 :     bool trailingDot = style.styleB(Sid::instrumentNumeralsTrailingDotSingle);
    1030              : 
    1031         8614 :     if (muse::contains(system->ldata()->partsWithGroupName(), part)) {
    1032          290 :         if (label.useCustomIndividualName()) {
    1033            0 :             return longNames ? label.customNameLongIndividual() : label.customNameShortIndividual();
    1034              :         }
    1035              : 
    1036          290 :         int number = part->number(tick);
    1037          290 :         String result = number > 0 ? String::number(number) : String();
    1038          290 :         if (!result.isEmpty() && trailingDot) {
    1039            0 :             result += '.';
    1040              :         }
    1041              : 
    1042          290 :         return result;
    1043          290 :     }
    1044              : 
    1045         8324 :     if (label.useCustomName()) {
    1046            0 :         return longNames ? label.customNameLong() : label.customNameShort();
    1047              :     }
    1048              : 
    1049         8324 :     String instrName = longNames ? instr->longName() : instr->shortName();
    1050              : 
    1051         8324 :     bool showTranspo = longNames ? label.showTranspositionLong() : label.showTranspositionShort();
    1052         8324 :     showTranspo &= style.styleB(longNames ? Sid::instrumentNamesShowTranspositionLong : Sid::instrumentNamesShowTranspositionShort);
    1053         8324 :     String transposition = showTranspo ? instr->transposition() : String();
    1054              : 
    1055         8324 :     bool showNumber = longNames ? label.showNumberLong() : label.showNumberShort();
    1056         8324 :     String number = instr->number() > 0 && showNumber ? String::number(instr->number()) : String();
    1057              : 
    1058              :     InstrumentNamesFormat nameFormat
    1059         8324 :         = style.styleV(longNames ? Sid::instrumentNamesFormatLong : Sid::instrumentNamesFormatShort).value<InstrumentNamesFormat>();
    1060              : 
    1061         8324 :     bool addTrailingDot = trailingDot && (transposition.empty() || nameFormat == InstrumentNamesFormat::NAME_IN_TRANSP_NUM
    1062            0 :                                           || nameFormat == InstrumentNamesFormat::TRANSP_NAME_NUM);
    1063         8324 :     if (addTrailingDot) {
    1064            0 :         number += '.';
    1065              :     }
    1066              : 
    1067         8324 :     if (transposition.empty()) {
    1068         8324 :         String result = instrName;
    1069         8324 :         if (!number.empty()) {
    1070            0 :             result += u" " + number;
    1071              :         }
    1072         8324 :         return result;
    1073         8324 :     }
    1074              : 
    1075              :     //: For instrument transposition, e.g. Horn in F
    1076            0 :     String in = TranslatableString("notation", "in").translated();
    1077              : 
    1078            0 :     switch (nameFormat) {
    1079            0 :     case InstrumentNamesFormat::NAME_IN_TRANSP_NUM:
    1080            0 :         return instrName + u" " + in + u" " + transposition + (number.empty() ? String() : u" " + number);
    1081            0 :     case InstrumentNamesFormat::NAME_NUM_IN_TRANSP:
    1082            0 :         return instrName + (number.empty() ? String() : u" " + number) + u" " + in + u" " + transposition;
    1083            0 :     case InstrumentNamesFormat::TRANSP_NAME_NUM:
    1084            0 :         return transposition + u" " + instrName + (number.empty() ? String() : u" " + number);
    1085            0 :     default:
    1086              :     {
    1087            0 :         String result = style.styleSt(longNames ? Sid::instrumentNamesCustomFormatLong : Sid::instrumentNamesCustomFormatShort);
    1088            0 :         return resolveTokens(result, instrName, transposition, number);
    1089            0 :     }
    1090              :     }
    1091         8324 : }
    1092              : 
    1093          375 : String SystemHeaderLayout::formattedGroupName(System* system, Part* part, const Fraction& tick, bool longNames)
    1094              : {
    1095          375 :     const MStyle& style = system->style();
    1096              : 
    1097          375 :     Instrument* instr = part->instrument(tick);
    1098          375 :     const InstrumentLabel& label = instr->instrumentLabel();
    1099              : 
    1100          375 :     if (label.useCustomGroupName()) {
    1101            0 :         return longNames ? label.customNameLongGroup() : label.customNameShortGroup();
    1102              :     }
    1103              : 
    1104          375 :     String instrName = longNames ? instr->longName() : instr->shortName();
    1105              : 
    1106          375 :     bool showTranspo = longNames ? label.showTranspositionLong() : label.showTranspositionShort();
    1107          375 :     showTranspo &= style.styleB(longNames ? Sid::instrumentNamesShowTranspositionLong : Sid::instrumentNamesShowTranspositionShort);
    1108              : 
    1109          375 :     String transposition = showTranspo ? instr->transposition() : String();
    1110              : 
    1111          375 :     if (transposition.empty()) {
    1112          375 :         return instrName;
    1113              :     }
    1114              : 
    1115              :     InstrumentNamesFormat nameFormat
    1116            0 :         = style.styleV(longNames ? Sid::instrumentNamesFormatLong : Sid::instrumentNamesFormatShort).value<InstrumentNamesFormat>();
    1117              : 
    1118              :     //: For instrument transposition, e.g. Horn in F
    1119            0 :     String in = TranslatableString("notation", "in").translated();
    1120              : 
    1121            0 :     switch (nameFormat) {
    1122            0 :     case InstrumentNamesFormat::NAME_IN_TRANSP_NUM:
    1123            0 :         return instrName + u" " + in + u" " + transposition;
    1124            0 :     case InstrumentNamesFormat::NAME_NUM_IN_TRANSP:
    1125            0 :         return instrName + u" " + in + u" " + transposition;
    1126            0 :     case InstrumentNamesFormat::TRANSP_NAME_NUM:
    1127            0 :         return transposition + u" " + instrName;
    1128            0 :     default:
    1129              :     {
    1130            0 :         String result = style.styleSt(longNames ? Sid::instrumentNamesCustomFormatLong : Sid::instrumentNamesCustomFormatShort);
    1131            0 :         return resolveTokens(result, instrName, transposition, /*number*/ String());
    1132            0 :     }
    1133              :     }
    1134          375 : }
    1135              : 
    1136          237 : String SystemHeaderLayout::formattedSharedStaffLabel(staff_idx_t staffIdx, const SharedTrackMap& trackMap,
    1137              :                                                      const std::vector<Part*>& originParts)
    1138              : {
    1139          237 :     if (originParts.empty()) {
    1140            0 :         return String();
    1141              :     }
    1142              : 
    1143          237 :     Score* score = originParts.front()->score();
    1144          237 :     const MStyle& style = score->style();
    1145          237 :     bool trailingDotSingle = style.styleB(Sid::instrumentNumeralsTrailingDotSingle);
    1146          237 :     bool trailingDotMultiple = style.styleB(Sid::instrumentNumeralsTrailingDotMultiple);
    1147          237 :     SharedLabelOrientation orientation = style.styleV(Sid::instrumentNumeralsOrientation).value<SharedLabelOrientation>();
    1148          237 :     int horizontalLimit = style.styleI(Sid::instrumentNumeralsHorizontalThreshold);
    1149          237 :     int verticalLimit = style.styleI(Sid::instrumentNumeralsVerticalThreshold);
    1150          237 :     int hyphenLimit = style.styleB(Sid::instrumentNumeralsHyphenEnable) ? style.styleI(Sid::instrumentNumeralsHyphenThreshold) : INT_MAX;
    1151              : 
    1152          237 :     std::vector<const Instrument*> instrumentsMappedToFirstVoice;
    1153          237 :     std::vector<const Instrument*> instrumentsMappedToSecondVoice;
    1154          702 :     for (auto [originTrack, sharedTrack] : trackMap) {
    1155          465 :         if (track2staff(sharedTrack) == staffIdx) {
    1156          237 :             if (track2voice(sharedTrack) == 0) {
    1157          232 :                 instrumentsMappedToFirstVoice.push_back(score->staff(track2staff(originTrack))->part()->instrument());
    1158              :             } else {
    1159            5 :                 instrumentsMappedToSecondVoice.push_back(score->staff(track2staff(originTrack))->part()->instrument());
    1160              :             }
    1161              :         }
    1162              :     }
    1163              : 
    1164          237 :     size_t totInstrumentCount = instrumentsMappedToFirstVoice.size() + instrumentsMappedToSecondVoice.size();
    1165              : 
    1166              :     SharedLabelOrientation actualOrientation;
    1167          237 :     switch (orientation) {
    1168            0 :     case SharedLabelOrientation::VOICE:
    1169            0 :         actualOrientation = orientation;
    1170            0 :         break;
    1171          237 :     case SharedLabelOrientation::HORIZONTAL:
    1172          237 :         actualOrientation = totInstrumentCount > size_t(horizontalLimit) ? SharedLabelOrientation::VOICE : orientation;
    1173          237 :         break;
    1174            0 :     case SharedLabelOrientation::VERTICAL:
    1175            0 :         actualOrientation = totInstrumentCount > size_t(verticalLimit) ? SharedLabelOrientation::VOICE : orientation;
    1176            0 :         break;
    1177            0 :     default: // should not happen, but don't leave actualOrientation uninitialized
    1178            0 :         ASSERT_X("Unexpected SharedLabelOrientation value: " << static_cast<int>(orientation));
    1179              :         actualOrientation = SharedLabelOrientation::VOICE;
    1180              :         break;
    1181              :     }
    1182              : 
    1183          237 :     if (actualOrientation == SharedLabelOrientation::HORIZONTAL || actualOrientation == SharedLabelOrientation::VERTICAL) {
    1184              :         // Put all instruments in single vector
    1185          237 :         instrumentsMappedToFirstVoice.insert(instrumentsMappedToFirstVoice.end(),
    1186              :                                              instrumentsMappedToSecondVoice.begin(), instrumentsMappedToSecondVoice.end());
    1187          237 :         std::sort(instrumentsMappedToFirstVoice.begin(), instrumentsMappedToFirstVoice.end(),
    1188          194 :                   [](const Instrument* a, const Instrument* b) { return a->number() < b->number(); });
    1189              : 
    1190          237 :         String result;
    1191              : 
    1192          237 :         if (actualOrientation == SharedLabelOrientation::HORIZONTAL) {
    1193          237 :             result = formatSharedVoiceLabel(instrumentsMappedToFirstVoice, trailingDotSingle, trailingDotMultiple, hyphenLimit);
    1194              :         } else {
    1195            0 :             result = formatVerticalSharedLabel(instrumentsMappedToFirstVoice, trailingDotSingle);
    1196              :         }
    1197              : 
    1198          237 :         return result;
    1199          237 :     }
    1200              : 
    1201            0 :     String result = formatSharedVoiceLabel(instrumentsMappedToFirstVoice, trailingDotSingle, trailingDotMultiple, hyphenLimit);
    1202              : 
    1203            0 :     if (instrumentsMappedToSecondVoice.empty()) {
    1204            0 :         return result;
    1205              :     }
    1206              : 
    1207            0 :     result += '\n' + formatSharedVoiceLabel(instrumentsMappedToSecondVoice, trailingDotSingle, trailingDotMultiple, hyphenLimit);
    1208              : 
    1209            0 :     return result;
    1210          237 : }
    1211              : 
    1212            0 : String SystemHeaderLayout::formatVerticalSharedLabel(const std::vector<const Instrument*>& instruments, bool trailingDotSingle)
    1213              : {
    1214            0 :     String result;
    1215              : 
    1216            0 :     for (const Instrument* instr : instruments) {
    1217            0 :         if (!result.empty()) {
    1218            0 :             result += '\n';
    1219              :         }
    1220              : 
    1221            0 :         result += String::number(instr->number());
    1222              : 
    1223            0 :         if (trailingDotSingle) {
    1224            0 :             result += '.';
    1225              :         }
    1226              :     }
    1227              : 
    1228            0 :     return result;
    1229            0 : }
    1230              : 
    1231          239 : String SystemHeaderLayout::formatSharedVoiceLabel(const std::vector<const Instrument*>& instruments, bool trailingDotSingle,
    1232              :                                                   bool trailingDotMultiple, int hyphenLimit)
    1233              : {
    1234          239 :     String result;
    1235              : 
    1236          239 :     size_t voiceCount = instruments.size();
    1237          239 :     bool putTrailingDot = (voiceCount <= 1 && trailingDotSingle) || (voiceCount > 1 && trailingDotMultiple);
    1238              : 
    1239          390 :     for (size_t i = 0; i < instruments.size(); ++i) {
    1240          151 :         if (!result.empty() && result.back() != '\n') {
    1241            9 :             result += '.';
    1242              :         }
    1243              : 
    1244          151 :         const Instrument* instr = instruments[i];
    1245          151 :         int startNumber = instr->number();
    1246          151 :         int curNumber = startNumber;
    1247          239 :         for (size_t j = i + 1; j < instruments.size(); ++j) {
    1248           97 :             const Instrument* nextInstr = instruments[j];
    1249           97 :             int nextNumber = nextInstr->number();
    1250           97 :             if (nextNumber == curNumber + 1) {
    1251           88 :                 curNumber = nextNumber;
    1252           88 :                 if (nextNumber - startNumber >= hyphenLimit) {
    1253           88 :                     i = j;
    1254              :                 } else {
    1255            0 :                     continue;
    1256              :                 }
    1257              :             } else {
    1258            9 :                 break;
    1259              :             }
    1260              :         }
    1261              : 
    1262          151 :         if (curNumber - startNumber >= hyphenLimit) {
    1263           88 :             result += String::number(startNumber);
    1264              : 
    1265           88 :             if (putTrailingDot) {
    1266            0 :                 result += '.';
    1267              :             }
    1268              : 
    1269           88 :             result += u'–' + String::number(curNumber);
    1270              :         } else {
    1271           63 :             result += String::number(startNumber);
    1272              :         }
    1273              :     }
    1274              : 
    1275          239 :     if (!result.isEmpty() && putTrailingDot) {
    1276            0 :         result += u".";
    1277              :     }
    1278              : 
    1279          239 :     return result;
    1280            0 : }
    1281              : 
    1282            0 : String& SystemHeaderLayout::resolveTokens(String& str, const String& name, const String& transposition, const String& number)
    1283              : {
    1284            0 :     static const String NAME = u"$name";
    1285            0 :     static const String TRANSP = u"$transposition";
    1286            0 :     static const String NUMBER = u"$number";
    1287              : 
    1288            0 :     str.replace(NAME, name);
    1289            0 :     str.replace(TRANSP, transposition);
    1290              : 
    1291            0 :     if (!number.empty()) {
    1292            0 :         str.replace(NUMBER, number);
    1293            0 :     } else if (str.contains(u" $number")) {
    1294            0 :         str.remove(u" $number");
    1295            0 :     } else if (str.contains(u"$number ")) {
    1296            0 :         str.remove(u"$number ");
    1297            0 :     } else if (str.contains(u"$number")) {
    1298            0 :         str.remove(u"$number");
    1299              :     }
    1300              : 
    1301            0 :     return str;
    1302              : }
    1303              : 
    1304        41883 : InstrumentLabelVisibility SystemHeaderLayout::resolveInstrumentLabelVisibility(const Staff* staff, const Fraction& tick,
    1305              :                                                                                LayoutContext& ctx, bool firstSystem)
    1306              : {
    1307        41883 :     const InstrumentLabelVisibility local = staff->staffType(tick)->instrumentLabelVisibility();
    1308        41883 :     if (local != InstrumentLabelVisibility::AUTO) {
    1309            0 :         return local;
    1310              :     }
    1311              : 
    1312        41883 :     if (ctx.conf().styleB(Sid::hideInstrumentNameIfOneInstrument) && ctx.dom().visiblePartCount() <= 1) {
    1313        23794 :         std::vector<Part*> visibleParts = ctx.dom().visibleParts();
    1314        23794 :         if (visibleParts.size() == 0) {
    1315            0 :             return InstrumentLabelVisibility::HIDE;
    1316              :         }
    1317        23794 :         if (visibleParts.size() == 1 && !visibleParts.front()->isSharedPart()) {
    1318        23054 :             return InstrumentLabelVisibility::HIDE;
    1319              :         }
    1320        23794 :     }
    1321              : 
    1322        18829 :     const Sid sid = firstSystem ? Sid::firstSystemInstNameVisibility : Sid::subsSystemInstNameVisibility;
    1323        18829 :     const InstrumentLabelVisibility styleVisibility = ctx.conf().styleV(sid).value<InstrumentLabelVisibility>();
    1324        18829 :     if (firstSystem && styleVisibility == InstrumentLabelVisibility::LONG && !ctx.state().startWithLongNames()) {
    1325            0 :         return InstrumentLabelVisibility::SHORT;
    1326              :     }
    1327        18829 :     return styleVisibility;
    1328              : }
    1329              : 
    1330        41883 : InstrumentNameType SystemHeaderLayout::resolveInstrumentNameType(InstrumentLabelVisibility visibility)
    1331              : {
    1332        41883 :     return visibility == InstrumentLabelVisibility::LONG ? InstrumentNameType::LONG : InstrumentNameType::SHORT;
    1333              : }
    1334              : 
    1335        14371 : void SystemHeaderLayout::setInstrumentNames(System* system, LayoutContext& ctx)
    1336              : {
    1337        14371 :     if (system->vbox()) {
    1338            8 :         return;
    1339              :     }
    1340              : 
    1341        14371 :     const Measure* firstMeasure = system->firstMeasure();
    1342        14371 :     if (!firstMeasure) {
    1343            8 :         return;
    1344              :     }
    1345              : 
    1346        14363 :     Fraction tick = firstMeasure->tick();
    1347              : 
    1348        14363 :     bool subsSysLongName = ctx.conf().styleV(Sid::subsSystemInstNameVisibility).value<InstrumentLabelVisibility>()
    1349        14363 :                            == InstrumentLabelVisibility::LONG;
    1350        14363 :     bool longName = ctx.mutState().firstSystem() ? ctx.mutState().startWithLongNames() : subsSysLongName;
    1351              : 
    1352        14363 :     System::LayoutData* ldata = system->mutldata();
    1353        14363 :     ldata->setUseLongNames(longName);
    1354              : 
    1355        14363 :     InstrumentNameType type = longName ? InstrumentNameType::LONG : InstrumentNameType::SHORT;
    1356              : 
    1357              :     // We do not show instrument names for video export
    1358        14363 :     if (!ctx.conf().isShowInstrumentNames()) {
    1359            0 :         for (staff_idx_t idx = 0; idx < system->staves().size(); ++idx) {
    1360            0 :             updateName(system, idx, ctx, String(), type, InstrumentNameRole::STAFF);
    1361            0 :             updateName(system, idx, ctx, String(), type, InstrumentNameRole::SHARED_STAFF);
    1362            0 :             updateName(system, idx, ctx, String(), type, InstrumentNameRole::PART);
    1363            0 :             updateName(system, idx, ctx, String(), type, InstrumentNameRole::GROUP);
    1364              :         }
    1365            0 :         return;
    1366              :     }
    1367              : 
    1368        14363 :     updateGroupNames(system, ctx, tick);
    1369              : 
    1370        14363 :     bool firstSystem = ctx.state().firstSystem();
    1371              : 
    1372        33605 :     for (size_t staffIdx = 0; staffIdx < system->staves().size(); /*empty*/) {
    1373        19242 :         Part* part = ctx.dom().staff(staffIdx)->part();
    1374        19242 :         size_t partNstaves = part->nstaves();
    1375        19242 :         size_t visibleStavesCount = part->visibleStavesCount();
    1376              : 
    1377        19242 :         if (part->isSharedPart() && part->show()) {
    1378          123 :             setSharedPartNames(toSharedPart(part), staffIdx, system, ctx, tick);
    1379          123 :             staffIdx += partNstaves;
    1380          123 :             continue;
    1381              :         }
    1382              : 
    1383        40662 :         for (size_t idxInPart = 0; idxInPart < partNstaves; ++idxInPart) {
    1384        21543 :             staff_idx_t globalIdx = staffIdx + idxInPart;
    1385        21543 :             const Staff* staff = ctx.dom().staff(globalIdx);
    1386              : 
    1387        21543 :             InstrumentLabelVisibility visibility = resolveInstrumentLabelVisibility(staff, tick, ctx, firstSystem);
    1388        21543 :             InstrumentNameType staffNameType = resolveInstrumentNameType(visibility);
    1389        21543 :             bool hide = visibility == InstrumentLabelVisibility::HIDE;
    1390        21543 :             bool staffLongName = staffNameType == InstrumentNameType::LONG;
    1391              : 
    1392        21543 :             String instrumentName;
    1393        21543 :             if (idxInPart == 0 && part->show() && visibleStavesCount > 0 && !hide) {
    1394         8614 :                 instrumentName = formattedInstrumentName(system, part, tick, staffLongName);
    1395              :             }
    1396        21543 :             updateName(system, globalIdx, ctx, instrumentName, staffNameType, InstrumentNameRole::PART);
    1397              : 
    1398        21543 :             String staffName;
    1399        21543 :             if (staff->show() && !hide) {
    1400         9490 :                 staffName = staffLongName ? staff->individualStaffNameLong(tick) : staff->individualStaffNameShort(tick);
    1401              :             }
    1402        21543 :             updateName(system, globalIdx, ctx, staffName, staffNameType, InstrumentNameRole::STAFF);
    1403        21543 :         }
    1404              : 
    1405        19119 :         staffIdx += partNstaves;
    1406              :     }
    1407              : }
    1408              : 
    1409          123 : void SystemHeaderLayout::setSharedPartNames(SharedPart* sharedPart, staff_idx_t startStaffIdx, System* system, LayoutContext& ctx,
    1410              :                                             const Fraction& tick)
    1411              : {
    1412              :     InstrumentLabelVisibility visibility
    1413          123 :         = resolveInstrumentLabelVisibility(ctx.dom().staff(startStaffIdx), tick, ctx, ctx.state().firstSystem());
    1414          123 :     InstrumentNameType type = resolveInstrumentNameType(visibility);
    1415          123 :     bool hideStartStaff = visibility == InstrumentLabelVisibility::HIDE;
    1416              : 
    1417          123 :     if (!sharedPart->show() || sharedPart->visibleStavesCount() == 0) {
    1418            0 :         for (size_t relStaffIdx = 0; relStaffIdx < sharedPart->nstaves(); ++relStaffIdx) {
    1419            0 :             size_t globalStaffIdx = startStaffIdx + relStaffIdx;
    1420            0 :             updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::GROUP);
    1421            0 :             updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::PART);
    1422            0 :             updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::SHARED_STAFF);
    1423            0 :             updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::STAFF);
    1424              :         }
    1425              : 
    1426            0 :         return;
    1427              :     }
    1428              : 
    1429          123 :     if (!sharedPart->isSameInstruments()) {
    1430              :         // TODO
    1431            0 :         return;
    1432              :     }
    1433              : 
    1434          123 :     const Instrument* instr = sharedPart->instrument();
    1435          123 :     bool useGroup = useGroupNames(instr->group(), ctx) && sharedPart->isSameInstrumentsAtTick(tick);
    1436              : 
    1437          123 :     const SharedTrackMap& trackMap = sharedPart->trackMapAtTick(tick);
    1438          123 :     const std::vector<Part*> originParts = sharedPart->originParts();
    1439              : 
    1440              :     // If using group names, assign normal PART name (one name covers all shared staves). Obtains the style
    1441              :     //
    1442              :     //      1.2
    1443              :     // Horn
    1444              :     //      3.4
    1445              :     //
    1446              :     // If not using group names, assign SHARED_STAFF name to each stave. Obtains the style
    1447              :     //
    1448              :     // Horn 1.2
    1449              :     //
    1450              :     // Horn 3.4
    1451              : 
    1452          360 :     for (size_t relStaffIdx = 0; relStaffIdx < sharedPart->nstaves(); ++relStaffIdx) {
    1453          237 :         size_t globalStaffIdx = startStaffIdx + relStaffIdx;
    1454          237 :         Part* originPart = sharedPart->originPartForStaff(globalStaffIdx, tick);
    1455              : 
    1456              :         InstrumentLabelVisibility partVisibility
    1457          237 :             = resolveInstrumentLabelVisibility(ctx.dom().staff(globalStaffIdx), tick, ctx, ctx.state().firstSystem());
    1458          237 :         InstrumentNameType partNameType = resolveInstrumentNameType(partVisibility);
    1459          237 :         bool partLongName = partNameType == InstrumentNameType::LONG;
    1460          237 :         bool hidePartName = partVisibility == InstrumentLabelVisibility::HIDE;
    1461              : 
    1462          237 :         String staffGroupName;
    1463          237 :         if (!hidePartName) {
    1464          237 :             staffGroupName = formattedGroupName(system, originPart ? originPart : sharedPart, tick, partLongName);
    1465              :         }
    1466              : 
    1467          237 :         if (useGroup && !hideStartStaff) {
    1468          237 :             if (relStaffIdx == 0) {
    1469          123 :                 updateName(system, globalStaffIdx, ctx, staffGroupName, partNameType, InstrumentNameRole::PART);
    1470              :             }
    1471              : 
    1472          237 :             updateName(system, globalStaffIdx, ctx, String(), partNameType, InstrumentNameRole::SHARED_STAFF);
    1473              :         } else {
    1474            0 :             if (relStaffIdx == 0) {
    1475            0 :                 updateName(system, globalStaffIdx, ctx, String(), type, InstrumentNameRole::PART);
    1476              :             }
    1477            0 :             updateName(system, globalStaffIdx, ctx, staffGroupName, partNameType, InstrumentNameRole::SHARED_STAFF);
    1478              :         }
    1479              : 
    1480          237 :         String staffLabel;
    1481          237 :         if (!hidePartName) {
    1482          237 :             staffLabel = formattedSharedStaffLabel(globalStaffIdx, trackMap, originParts);
    1483              :         }
    1484              : 
    1485          237 :         updateName(system, globalStaffIdx, ctx, staffLabel, partNameType, InstrumentNameRole::STAFF);
    1486          237 :     }
    1487          123 : }
    1488              : 
    1489        14363 : void SystemHeaderLayout::updateGroupNames(System* system, LayoutContext& ctx, const Fraction& tick)
    1490              : {
    1491        14363 :     bool firstSystem = ctx.state().firstSystem();
    1492              : 
    1493        14363 :     System::LayoutData* ldata = system->mutldata();
    1494        14363 :     ldata->clearPartsWithGroupNames();
    1495              : 
    1496        32015 :     for (staff_idx_t startOfGroup = 0; startOfGroup < system->staves().size();) {
    1497        17652 :         Part* curPart = ctx.dom().staff(startOfGroup)->part();
    1498        17652 :         const Instrument* curInstrument = curPart->instrument(tick);
    1499        17652 :         const InstrumentLabel& curLabel = curInstrument->instrumentLabel();
    1500        17652 :         if (!curInstrument->instrumentLabel().allowGroupName() || !curPart->show() || curPart->isSharedPart()) {
    1501          488 :             ++startOfGroup;
    1502          488 :             continue;
    1503              :         }
    1504              : 
    1505              :         InstrumentLabelVisibility visibility
    1506        17164 :             = resolveInstrumentLabelVisibility(ctx.dom().staff(startOfGroup), tick, ctx, firstSystem);
    1507        17164 :         InstrumentNameType type = resolveInstrumentNameType(visibility);
    1508        17164 :         bool hide = visibility == InstrumentLabelVisibility::HIDE;
    1509              : 
    1510        17164 :         std::vector<Part*> partsInThisGroup;
    1511              : 
    1512        17164 :         staff_idx_t endOfGroup = startOfGroup;
    1513        36038 :         while (endOfGroup < system->staves().size()) {
    1514        21800 :             Part* nextPart = ctx.dom().staff(endOfGroup)->part();
    1515        21800 :             Instrument* nextInstrument = nextPart->instrument(tick);
    1516        21800 :             InstrumentLabel& nextLabel = nextInstrument->instrumentLabel();
    1517        43600 :             if (nextPart != curPart
    1518        21800 :                 && (nextInstrument->id() != curInstrument->id() || !nextLabel.allowGroupName() || nextPart->isSharedPart())) {
    1519         2926 :                 break;
    1520              :             }
    1521        37748 :             if (nextLabel.useCustomGroupName() && (nextLabel.customNameLongGroup() != curLabel.customNameLongGroup()
    1522        18874 :                                                    || nextLabel.customNameShortGroup() != curLabel.customNameShortGroup())) {
    1523            0 :                 break;
    1524              :             }
    1525              : 
    1526        18874 :             if (type == InstrumentNameType::LONG && system->visibleStavesOfPart(nextPart).size() == 0) {
    1527              :                 // NOTE: We can only do this for long-name systems because they don't need to have
    1528              :                 // the left barline aligned with the other systems across the page.
    1529           17 :                 endOfGroup += nextPart->nstaves();
    1530           17 :                 continue;
    1531              :             }
    1532              : 
    1533        18857 :             if (nextPart->show() && nextPart->visibleStavesCount() > 0) {
    1534        18841 :                 partsInThisGroup.push_back(nextPart);
    1535        18841 :                 nextLabel.setUseCustomGroupName(curLabel.useCustomGroupName());
    1536        18841 :                 nextLabel.setCustomNameLongGroup(curLabel.customNameLongGroup());
    1537        18841 :                 nextLabel.setCustomNameShortGroup(curLabel.customNameShortGroup());
    1538              :             }
    1539              : 
    1540        18857 :             endOfGroup += nextPart->nstaves();
    1541              :         }
    1542              : 
    1543        17164 :         if (!hide && partsInThisGroup.size() > 1 && useGroupNames(curInstrument->group(), ctx)) {
    1544          138 :             String name = formattedGroupName(system, curPart, tick, type == InstrumentNameType::LONG);
    1545          138 :             InstrumentName* groupName = updateName(system, startOfGroup, ctx, name, type, InstrumentNameRole::GROUP);
    1546              : 
    1547          138 :             if (groupName) {
    1548          134 :                 groupName->mutldata()->setEndIdxOfGroup(endOfGroup);
    1549              : 
    1550          424 :                 for (Part* p : partsInThisGroup) {
    1551          290 :                     ldata->addPartWithGroupNames(p, groupName);
    1552              :                 }
    1553              :             }
    1554              : 
    1555          302 :             for (staff_idx_t idx = startOfGroup + 1; idx < endOfGroup; ++idx) {
    1556          164 :                 updateName(system, idx, ctx, String(), type, InstrumentNameRole::GROUP);
    1557              :             }
    1558          138 :         } else {
    1559        38016 :             for (staff_idx_t idx = startOfGroup; idx < endOfGroup; ++idx) {
    1560        20990 :                 updateName(system, idx, ctx, String(), type, InstrumentNameRole::GROUP);
    1561              :             }
    1562              :         }
    1563              : 
    1564        17164 :         startOfGroup = endOfGroup;
    1565        17164 :     }
    1566              : 
    1567        36143 :     for (staff_idx_t staffIdx = 0; staffIdx < system->staves().size(); ++staffIdx) {
    1568        21780 :         if (system->staff(staffIdx)->name(InstrumentNameRole::GROUP)) {
    1569          134 :             Part* part = ctx.dom().staff(staffIdx)->part();
    1570          134 :             if (!muse::contains(ldata->partsWithGroupName(), part)) {
    1571              :                 // name is empty here, so the InstrumentNameType is unused (see updateName)
    1572            0 :                 updateName(system, staffIdx, ctx, String(), InstrumentNameType::SHORT, InstrumentNameRole::GROUP);
    1573              :             }
    1574              :         }
    1575              :     }
    1576        14363 : }
    1577              : 
    1578          859 : bool SystemHeaderLayout::useGroupNames(const String& instrumentGroup, LayoutContext& ctx)
    1579              : {
    1580          859 :     const MStyle& style = ctx.conf().style();
    1581              : 
    1582          859 :     if (instrumentGroup == "woodwinds" || instrumentGroup == "brass") {
    1583          351 :         return style.styleB(Sid::windsNameByGroup);
    1584              :     }
    1585          508 :     if (instrumentGroup == "vocals") {
    1586           52 :         return style.styleB(Sid::vocalsNameByGroup);
    1587              :     }
    1588          456 :     if (instrumentGroup == "strings") {
    1589           84 :         return style.styleB(Sid::stringsNameByGroup);
    1590              :     }
    1591              : 
    1592          372 :     return style.styleB(Sid::othersNameByGroup);
    1593              : }
    1594              : 
    1595        14374 : void SystemHeaderLayout::updateSystemHeaderWidth(System* system, LayoutContext& ctx)
    1596              : {
    1597        14374 :     if (system->staves().empty()) {                 // ignore vbox
    1598            3 :         return;
    1599              :     }
    1600              : 
    1601        14371 :     SystemHeaderLayout::computeInstrumentNameOffset(system, ctx);
    1602        14371 :     double instrumentNameOffset = system->ldata()->instrumentNameOffset();
    1603              : 
    1604              :     //---------------------------------------------------
    1605              :     //  find x position of staves
    1606              :     //---------------------------------------------------
    1607        14371 :     SystemHeaderLayout::layoutBrackets(system, ctx);
    1608        14371 :     double maxBracketsWidth = SystemHeaderLayout::totalBracketOffset(ctx);
    1609              : 
    1610        14371 :     SystemHeaderLayout::setInstrumentNames(system, ctx);
    1611        14371 :     SystemHeaderLayout::computeInstrumentNamesWidth(system, ctx);
    1612        14371 :     double maxNamesWidth = system->ldata()->totalNamesWidth();
    1613        14371 :     double indent = maxNamesWidth > 0 ? maxNamesWidth + instrumentNameOffset : 0.0;
    1614        14371 :     if (ctx.state().firstSystem() && ctx.state().firstSystemIndent()) {
    1615         5973 :         indent = std::max(indent, system->styleP(Sid::firstSystemIndentationValue) * system->mag() - maxBracketsWidth);
    1616         5973 :         maxNamesWidth = indent - instrumentNameOffset;
    1617              :     }
    1618              : 
    1619        14371 :     if (muse::RealIsNull(indent)) {
    1620         6628 :         if (ctx.conf().styleB(Sid::alignSystemToMargin)) {
    1621         6628 :             system->setLeftMargin(0.0);
    1622              :         } else {
    1623            0 :             system->setLeftMargin(maxBracketsWidth);
    1624              :         }
    1625              :     } else {
    1626         7743 :         system->setLeftMargin(indent + maxBracketsWidth);
    1627              :     }
    1628              : }
        

Generated by: LCOV version 2.0-1