From f4361a734661a67889b9840847b2f9107fc316d2 Mon Sep 17 00:00:00 2001 From: ajuncosa Date: Mon, 25 May 2026 19:38:28 +0200 Subject: [PATCH 1/6] Fix header/footer timestamps update on file save --- src/engraving/dom/score.cpp | 5 ++++ src/engraving/dom/score.h | 1 + src/engraving/rendering/iscorerenderer.h | 1 + src/engraving/rendering/score/scorelayout.cpp | 25 +++++++++++++++++++ src/engraving/rendering/score/scorelayout.h | 1 + .../rendering/score/scorepageviewlayout.cpp | 23 ++++++++++++++--- .../rendering/score/scorepageviewlayout.h | 3 +++ .../rendering/score/scorerenderer.cpp | 5 ++++ src/engraving/rendering/score/scorerenderer.h | 1 + src/project/internal/notationproject.cpp | 3 +++ 10 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index 053df9a884adc..b274220884e1c 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -5979,6 +5979,11 @@ void Score::doLayoutRange(const Fraction& st, const Fraction& et) } } +void Score::doLayoutHeadersFooters() +{ + renderer()->layoutHeadersFooters(this); +} + void Score::createPaddingTable() { m_paddingTable.createTable(style()); diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index 32c0a8df8e035..ba1350629bf96 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -888,6 +888,7 @@ class Score : public EngravingObject, public muse::Contextable void doLayout(); void doLayoutRange(const Fraction& st, const Fraction& et); + void doLayoutHeadersFooters(); SynthesizerState& synthesizerState() { return m_synthesizerState; } void setSynthesizerState(const SynthesizerState& s); diff --git a/src/engraving/rendering/iscorerenderer.h b/src/engraving/rendering/iscorerenderer.h index e02a6bd0d3e21..6f29c051f2135 100644 --- a/src/engraving/rendering/iscorerenderer.h +++ b/src/engraving/rendering/iscorerenderer.h @@ -105,6 +105,7 @@ class IScoreRenderer : MODULE_CONTEXT_INTERFACE // Main interface virtual void layoutScore(Score* score, const Fraction& st, const Fraction& et) const = 0; + virtual void layoutHeadersFooters(Score* score) const = 0; struct ScorePaintOptions : public PaintOptions { diff --git a/src/engraving/rendering/score/scorelayout.cpp b/src/engraving/rendering/score/scorelayout.cpp index fa57113d892c0..c62779293b268 100644 --- a/src/engraving/rendering/score/scorelayout.cpp +++ b/src/engraving/rendering/score/scorelayout.cpp @@ -97,3 +97,28 @@ void ScoreLayout::layoutRange(Score* score, const Fraction& st, const Fraction& //LOGDA() << DumpLayoutData::dump(score); } + +void ScoreLayout::layoutHeadersFooters(Score* score) +{ + TRACEFUNC; + + CmdStateLocker cmdStateLocker(score); + LayoutContext ctx(score); + + if (!ctx.conf().isMode(LayoutMode::PAGE) && !ctx.conf().isMode(LayoutMode::FLOAT)) { + return; + } + + // Check empty score + if (!score->last()) { + LOGD() << "empty score"; + muse::DeleteAll(score->systems()); + score->systems().clear(); + muse::DeleteAll(score->pages()); + score->pages().clear(); + PageLayout::getNextPage(ctx); + return; + } + + ScorePageViewLayout::layoutHeadersFooters(ctx); +} diff --git a/src/engraving/rendering/score/scorelayout.h b/src/engraving/rendering/score/scorelayout.h index f5e942d1a56e0..66a034e75c36e 100644 --- a/src/engraving/rendering/score/scorelayout.h +++ b/src/engraving/rendering/score/scorelayout.h @@ -34,6 +34,7 @@ class ScoreLayout public: static void layoutRange(Score* score, const Fraction& st, const Fraction& et); + static void layoutHeadersFooters(Score* score); }; } diff --git a/src/engraving/rendering/score/scorepageviewlayout.cpp b/src/engraving/rendering/score/scorepageviewlayout.cpp index 3322bab6f816c..9119178746270 100644 --- a/src/engraving/rendering/score/scorepageviewlayout.cpp +++ b/src/engraving/rendering/score/scorepageviewlayout.cpp @@ -257,9 +257,26 @@ void ScorePageViewLayout::layoutFinished(Score* score, LayoutContext& ctx) /* If the headers/footers involve page count, we need to re-calculate them. This needs to * be done after laying out all pages, so that the total number of pages will be known: */ if (state.mustRecomputeHeadersFooters()) { - for (const auto& page : ctx.mutDom().pages()) { - HeaderFooterLayout::layoutHeaderFooter(ctx, page); - } + doLayoutHeadersFooters(ctx); state.setMustRecomputeHeadersFooters(false); } } + +void ScorePageViewLayout::layoutHeadersFooters(LayoutContext& ctx) +{ + TRACEFUNC; + + LAYOUT_CALL_CLEAR(); + LAYOUT_CALL(); + + doLayoutHeadersFooters(ctx); + + LAYOUT_CALL_PRINT(); +} + +void ScorePageViewLayout::doLayoutHeadersFooters(LayoutContext& ctx) +{ + for (const auto& page : ctx.dom().pages()) { + HeaderFooterLayout::layoutHeaderFooter(ctx, page); + } +} diff --git a/src/engraving/rendering/score/scorepageviewlayout.h b/src/engraving/rendering/score/scorepageviewlayout.h index aa8b766cb51a7..185e999f652c3 100644 --- a/src/engraving/rendering/score/scorepageviewlayout.h +++ b/src/engraving/rendering/score/scorepageviewlayout.h @@ -30,6 +30,7 @@ class ScorePageViewLayout public: static void layoutPageView(Score* score, LayoutContext& ctx, const Fraction& stick, const Fraction& etick); + static void layoutHeadersFooters(LayoutContext& ctx); private: @@ -38,6 +39,8 @@ class ScorePageViewLayout static void doLayout(LayoutContext& ctx); + static void doLayoutHeadersFooters(LayoutContext& ctx); + static void layoutFinished(Score* score, LayoutContext& ctx); }; } diff --git a/src/engraving/rendering/score/scorerenderer.cpp b/src/engraving/rendering/score/scorerenderer.cpp index 6c33954ad5b70..70d07f6eceb5d 100644 --- a/src/engraving/rendering/score/scorerenderer.cpp +++ b/src/engraving/rendering/score/scorerenderer.cpp @@ -49,6 +49,11 @@ void ScoreRenderer::layoutScore(Score* score, const Fraction& st, const Fraction ScoreLayout::layoutRange(score, st, et); } +void ScoreRenderer::layoutHeadersFooters(Score* score) const +{ + ScoreLayout::layoutHeadersFooters(score); +} + SizeF ScoreRenderer::pageSizeInch(const Score* score) const { return Paint::pageSizeInch(score); diff --git a/src/engraving/rendering/score/scorerenderer.h b/src/engraving/rendering/score/scorerenderer.h index 9392e3aacd2c1..c8652d20601ff 100644 --- a/src/engraving/rendering/score/scorerenderer.h +++ b/src/engraving/rendering/score/scorerenderer.h @@ -34,6 +34,7 @@ class ScoreRenderer : public IScoreRenderer // Main interface void layoutScore(Score* score, const Fraction& st, const Fraction& et) const override; + void layoutHeadersFooters(Score* score) const override; SizeF pageSizeInch(const Score* score) const override; SizeF pageSizeInch(const Score* score, const ScorePaintOptions& opt) const override; diff --git a/src/project/internal/notationproject.cpp b/src/project/internal/notationproject.cpp index e3413ef927fda..6657246bb7c4a 100644 --- a/src/project/internal/notationproject.cpp +++ b/src/project/internal/notationproject.cpp @@ -518,6 +518,9 @@ Ret NotationProject::save(const muse::io::path_t& path, SaveMode saveMode, bool if (saveMode != SaveMode::SaveCopy) { markAsSaved(savePath); } + + // Re-compute headers and footers on save, to force timestamps update (if any) + m_engravingProject->masterScore()->doLayoutHeadersFooters(); } } break; case SaveMode::AutoSave: { From a9e860ecf9a98ce3250b201bfef518fd8dc858c7 Mon Sep 17 00:00:00 2001 From: ajuncosa Date: Thu, 28 May 2026 15:17:01 +0200 Subject: [PATCH 2/6] Last modified timestamp on headers/footers to update only on save --- .../rendering/score/headerfooterlayout.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/engraving/rendering/score/headerfooterlayout.cpp b/src/engraving/rendering/score/headerfooterlayout.cpp index 1d1270ff8eff6..6f1108dcae7a7 100644 --- a/src/engraving/rendering/score/headerfooterlayout.cpp +++ b/src/engraving/rendering/score/headerfooterlayout.cpp @@ -322,21 +322,12 @@ TextBlock HeaderFooterLayout::replaceTextMacros(LayoutContext& ctx, const Page* break; case 'm': { IFileInfoProviderPtr fileInfo = page->score()->masterScore()->fileInfo(); - if (page->score()->dirty() || !fileInfo->saved()) { - newFragments.back().text += muse::Time::currentTime().toString(muse::DateFormat::ISODate); - } else { - newFragments.back().text += fileInfo->lastModified().time().toString( - muse::DateFormat::ISODate); - } + newFragments.back().text += fileInfo->lastModified().time().toString(muse::DateFormat::ISODate); } break; case 'M': { IFileInfoProviderPtr fileInfo = page->score()->masterScore()->fileInfo(); - if (page->score()->dirty() || !fileInfo->saved()) { - newFragments.back().text += muse::Date::currentDate().toString(muse::DateFormat::ISODate); - } else { - newFragments.back().text += fileInfo->lastModified().date().toString(muse::DateFormat::ISODate); - } + newFragments.back().text += fileInfo->lastModified().date().toString(muse::DateFormat::ISODate); } break; case 'C': // only on first page From 917a85f5ccdbbc0d053052e336478ed046db8374 Mon Sep 17 00:00:00 2001 From: ajuncosa Date: Mon, 1 Jun 2026 11:51:15 +0200 Subject: [PATCH 3/6] Refactor header/footer re-layout trigger from NotationProject: inject renderer as a dependency in NotationProject directly to decouple DOM and layout, and remove intermediate calls between renderer and ScorePageViewLayout --- src/engraving/dom/score.cpp | 5 ---- src/engraving/dom/score.h | 1 - src/engraving/rendering/score/scorelayout.cpp | 25 ------------------- src/engraving/rendering/score/scorelayout.h | 1 - .../rendering/score/scorerenderer.cpp | 8 +++++- src/project/internal/notationproject.cpp | 2 +- src/project/internal/notationproject.h | 2 ++ 7 files changed, 10 insertions(+), 34 deletions(-) diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index b274220884e1c..053df9a884adc 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -5979,11 +5979,6 @@ void Score::doLayoutRange(const Fraction& st, const Fraction& et) } } -void Score::doLayoutHeadersFooters() -{ - renderer()->layoutHeadersFooters(this); -} - void Score::createPaddingTable() { m_paddingTable.createTable(style()); diff --git a/src/engraving/dom/score.h b/src/engraving/dom/score.h index ba1350629bf96..32c0a8df8e035 100644 --- a/src/engraving/dom/score.h +++ b/src/engraving/dom/score.h @@ -888,7 +888,6 @@ class Score : public EngravingObject, public muse::Contextable void doLayout(); void doLayoutRange(const Fraction& st, const Fraction& et); - void doLayoutHeadersFooters(); SynthesizerState& synthesizerState() { return m_synthesizerState; } void setSynthesizerState(const SynthesizerState& s); diff --git a/src/engraving/rendering/score/scorelayout.cpp b/src/engraving/rendering/score/scorelayout.cpp index c62779293b268..fa57113d892c0 100644 --- a/src/engraving/rendering/score/scorelayout.cpp +++ b/src/engraving/rendering/score/scorelayout.cpp @@ -97,28 +97,3 @@ void ScoreLayout::layoutRange(Score* score, const Fraction& st, const Fraction& //LOGDA() << DumpLayoutData::dump(score); } - -void ScoreLayout::layoutHeadersFooters(Score* score) -{ - TRACEFUNC; - - CmdStateLocker cmdStateLocker(score); - LayoutContext ctx(score); - - if (!ctx.conf().isMode(LayoutMode::PAGE) && !ctx.conf().isMode(LayoutMode::FLOAT)) { - return; - } - - // Check empty score - if (!score->last()) { - LOGD() << "empty score"; - muse::DeleteAll(score->systems()); - score->systems().clear(); - muse::DeleteAll(score->pages()); - score->pages().clear(); - PageLayout::getNextPage(ctx); - return; - } - - ScorePageViewLayout::layoutHeadersFooters(ctx); -} diff --git a/src/engraving/rendering/score/scorelayout.h b/src/engraving/rendering/score/scorelayout.h index 66a034e75c36e..f5e942d1a56e0 100644 --- a/src/engraving/rendering/score/scorelayout.h +++ b/src/engraving/rendering/score/scorelayout.h @@ -34,7 +34,6 @@ class ScoreLayout public: static void layoutRange(Score* score, const Fraction& st, const Fraction& et); - static void layoutHeadersFooters(Score* score); }; } diff --git a/src/engraving/rendering/score/scorerenderer.cpp b/src/engraving/rendering/score/scorerenderer.cpp index 70d07f6eceb5d..c298f69a616e4 100644 --- a/src/engraving/rendering/score/scorerenderer.cpp +++ b/src/engraving/rendering/score/scorerenderer.cpp @@ -33,6 +33,7 @@ #include "chordlayout.h" #include "layoutcontext.h" #include "scorelayout.h" +#include "scorepageviewlayout.h" #include "horizontalspacing.h" #include "slurtielayout.h" @@ -51,7 +52,12 @@ void ScoreRenderer::layoutScore(Score* score, const Fraction& st, const Fraction void ScoreRenderer::layoutHeadersFooters(Score* score) const { - ScoreLayout::layoutHeadersFooters(score); + LayoutContext ctx(score); + if (!ctx.conf().isMode(LayoutMode::PAGE) && !ctx.conf().isMode(LayoutMode::FLOAT)) { + return; + } + + ScorePageViewLayout::layoutHeadersFooters(ctx); } SizeF ScoreRenderer::pageSizeInch(const Score* score) const diff --git a/src/project/internal/notationproject.cpp b/src/project/internal/notationproject.cpp index 6657246bb7c4a..871f08a7bf941 100644 --- a/src/project/internal/notationproject.cpp +++ b/src/project/internal/notationproject.cpp @@ -520,7 +520,7 @@ Ret NotationProject::save(const muse::io::path_t& path, SaveMode saveMode, bool } // Re-compute headers and footers on save, to force timestamps update (if any) - m_engravingProject->masterScore()->doLayoutHeadersFooters(); + renderer()->layoutHeadersFooters(m_engravingProject->masterScore()); } } break; case SaveMode::AutoSave: { diff --git a/src/project/internal/notationproject.h b/src/project/internal/notationproject.h index 155a7ba8beacf..99457fabca4e4 100644 --- a/src/project/internal/notationproject.h +++ b/src/project/internal/notationproject.h @@ -32,6 +32,7 @@ #include "inotationwritersregister.h" #include "engraving/engravingproject.h" +#include "engraving/rendering/iscorerenderer.h" #include "notation/inotationconfiguration.h" #include "projectaudiosettings.h" @@ -58,6 +59,7 @@ class NotationProject : public INotationProject, public muse::Contextable, publi muse::ContextInject readers = { this }; muse::ContextInject writers = { this }; muse::ContextInject migrator = { this }; + muse::ContextInject renderer = { this }; public: NotationProject(const muse::modularity::ContextPtr& iocCtx) From e04e2345f0c2fe6adb23984cc31577017cfdb653 Mon Sep 17 00:00:00 2001 From: ajuncosa Date: Wed, 3 Jun 2026 13:00:23 +0200 Subject: [PATCH 4/6] Add header/footer timestamps placeholder for newly created scores --- src/engraving/infrastructure/ifileinfoprovider.h | 1 + .../infrastructure/localfileinfoprovider.cpp | 5 +++++ src/engraving/infrastructure/localfileinfoprovider.h | 1 + src/engraving/rendering/score/headerfooterlayout.cpp | 12 ++++++++++-- src/project/internal/projectfileinfoprovider.cpp | 5 +++++ src/project/internal/projectfileinfoprovider.h | 1 + 6 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/engraving/infrastructure/ifileinfoprovider.h b/src/engraving/infrastructure/ifileinfoprovider.h index 3e89b1b96356e..e8ddcee7a07b9 100644 --- a/src/engraving/infrastructure/ifileinfoprovider.h +++ b/src/engraving/infrastructure/ifileinfoprovider.h @@ -43,6 +43,7 @@ class IFileInfoProvider virtual muse::DateTime birthTime() const = 0; virtual muse::DateTime lastModified() const = 0; + virtual bool isNewlyCreated() const = 0; }; using IFileInfoProviderPtr = std::shared_ptr; diff --git a/src/engraving/infrastructure/localfileinfoprovider.cpp b/src/engraving/infrastructure/localfileinfoprovider.cpp index 4593e5f0109da..59f288d1c1b24 100644 --- a/src/engraving/infrastructure/localfileinfoprovider.cpp +++ b/src/engraving/infrastructure/localfileinfoprovider.cpp @@ -65,3 +65,8 @@ DateTime LocalFileInfoProvider::lastModified() const { return fileSystem()->lastModified(m_path); } + +bool LocalFileInfoProvider::isNewlyCreated() const +{ + return true; +} diff --git a/src/engraving/infrastructure/localfileinfoprovider.h b/src/engraving/infrastructure/localfileinfoprovider.h index 5e26f565d0c14..771a5329ca0a7 100644 --- a/src/engraving/infrastructure/localfileinfoprovider.h +++ b/src/engraving/infrastructure/localfileinfoprovider.h @@ -45,6 +45,7 @@ class LocalFileInfoProvider : public IFileInfoProvider muse::DateTime birthTime() const override; muse::DateTime lastModified() const override; + bool isNewlyCreated() const override; private: muse::io::path_t m_path; diff --git a/src/engraving/rendering/score/headerfooterlayout.cpp b/src/engraving/rendering/score/headerfooterlayout.cpp index 6f1108dcae7a7..555fc76f4f789 100644 --- a/src/engraving/rendering/score/headerfooterlayout.cpp +++ b/src/engraving/rendering/score/headerfooterlayout.cpp @@ -322,12 +322,20 @@ TextBlock HeaderFooterLayout::replaceTextMacros(LayoutContext& ctx, const Page* break; case 'm': { IFileInfoProviderPtr fileInfo = page->score()->masterScore()->fileInfo(); - newFragments.back().text += fileInfo->lastModified().time().toString(muse::DateFormat::ISODate); + if (fileInfo->isNewlyCreated()) { + newFragments.back().text += String(u"HH:mm:ss"); + } else { + newFragments.back().text += fileInfo->lastModified().time().toString(muse::DateFormat::ISODate); + } } break; case 'M': { IFileInfoProviderPtr fileInfo = page->score()->masterScore()->fileInfo(); - newFragments.back().text += fileInfo->lastModified().date().toString(muse::DateFormat::ISODate); + if (fileInfo->isNewlyCreated()) { + newFragments.back().text += String(u"YYYY-MM-DD"); + } else { + newFragments.back().text += fileInfo->lastModified().date().toString(muse::DateFormat::ISODate); + } } break; case 'C': // only on first page diff --git a/src/project/internal/projectfileinfoprovider.cpp b/src/project/internal/projectfileinfoprovider.cpp index 44f9ae5c022d3..42d4c0260c093 100644 --- a/src/project/internal/projectfileinfoprovider.cpp +++ b/src/project/internal/projectfileinfoprovider.cpp @@ -67,3 +67,8 @@ DateTime ProjectFileInfoProvider::lastModified() const { return filesystem()->lastModified(path()); } + +bool ProjectFileInfoProvider::isNewlyCreated() const +{ + return m_project->isNewlyCreated(); +} diff --git a/src/project/internal/projectfileinfoprovider.h b/src/project/internal/projectfileinfoprovider.h index df49d55a07285..51d0ac9670c72 100644 --- a/src/project/internal/projectfileinfoprovider.h +++ b/src/project/internal/projectfileinfoprovider.h @@ -46,6 +46,7 @@ class ProjectFileInfoProvider : public engraving::IFileInfoProvider muse::DateTime birthTime() const override; muse::DateTime lastModified() const override; + bool isNewlyCreated() const override; private: NotationProject* m_project = nullptr; From 9e2ccc1afe471c5c1cd45226931bfcb8bd9a3aeb Mon Sep 17 00:00:00 2001 From: ajuncosa Date: Wed, 3 Jun 2026 17:15:57 +0200 Subject: [PATCH 5/6] Header/footer timestamps update triggered on save for all open parts --- src/project/internal/notationproject.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/project/internal/notationproject.cpp b/src/project/internal/notationproject.cpp index 871f08a7bf941..08dda7cacaed3 100644 --- a/src/project/internal/notationproject.cpp +++ b/src/project/internal/notationproject.cpp @@ -33,6 +33,7 @@ #include "global/io/devtools/allzerosfilecorruptor.h" #include "engraving/compat/engravingcompat.h" +#include "engraving/dom/excerpt.h" #include "engraving/dom/masterscore.h" #include "engraving/dom/repeatlist.h" #include "engraving/editing/editscoreproperties.h" @@ -520,7 +521,15 @@ Ret NotationProject::save(const muse::io::path_t& path, SaveMode saveMode, bool } // Re-compute headers and footers on save, to force timestamps update (if any) - renderer()->layoutHeadersFooters(m_engravingProject->masterScore()); + const std::list& scores = m_engravingProject->masterScore()->scoreList(); + for (Score* s : scores) { + renderer()->layoutHeadersFooters(s); + } + for (const IExcerptNotationPtr& excerptNotation : m_masterNotation->excerpts()) { + if (excerptNotation->isInited() && excerptNotation->notation()->isOpen()) { + excerptNotation->notation()->notationChanged().notify(); + } + } } } break; case SaveMode::AutoSave: { From 870f4a33e1808334d1b85779a2ad982f27137a2b Mon Sep 17 00:00:00 2001 From: ajuncosa Date: Wed, 3 Jun 2026 18:39:37 +0200 Subject: [PATCH 6/6] Refactor timestamp update in headers/footers on save, to avoid relayout and redraw of all excerpts if not necessary --- src/engraving/rendering/iscorerenderer.h | 2 + .../rendering/score/headerfooterlayout.cpp | 37 +++++++++++++++++++ .../rendering/score/headerfooterlayout.h | 4 ++ .../rendering/score/scorerenderer.cpp | 9 +++++ src/engraving/rendering/score/scorerenderer.h | 2 + src/project/internal/notationproject.cpp | 15 ++++---- src/project/internal/notationproject.h | 2 + 7 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/engraving/rendering/iscorerenderer.h b/src/engraving/rendering/iscorerenderer.h index 6f29c051f2135..e0e4d5cda5ffe 100644 --- a/src/engraving/rendering/iscorerenderer.h +++ b/src/engraving/rendering/iscorerenderer.h @@ -200,6 +200,8 @@ class IScoreRenderer : MODULE_CONTEXT_INTERFACE virtual void computeBezier(TieSegment* tieSeg, PointF shoulderOffset = PointF()) = 0; virtual void computeBezier(SlurSegment* slurSeg, PointF shoulderOffser = PointF()) = 0; + virtual bool scoreHasTimestampHeadersFooters(const Score* score) const = 0; + private: // Layout Single Item virtual void doLayoutItem(EngravingItem* item) = 0; diff --git a/src/engraving/rendering/score/headerfooterlayout.cpp b/src/engraving/rendering/score/headerfooterlayout.cpp index 555fc76f4f789..68077a70257a2 100644 --- a/src/engraving/rendering/score/headerfooterlayout.cpp +++ b/src/engraving/rendering/score/headerfooterlayout.cpp @@ -488,3 +488,40 @@ double HeaderFooterLayout::footerExtension(const LayoutContext& ctx, const Page* } return 0.0; } + +bool HeaderFooterLayout::containsTimestampMacros(const String& text) +{ + return text.contains(u"$d") + || text.contains(u"$D") + || text.contains(u"$m") + || text.contains(u"$M"); +} + +bool HeaderFooterLayout::scoreHasTimestampHeadersFooters(const Score* score) +{ + const MStyle& style = score->style(); + + if (style.styleB(Sid::showHeader)) { + if (containsTimestampMacros(style.styleSt(Sid::oddHeaderL)) + || containsTimestampMacros(style.styleSt(Sid::oddHeaderC)) + || containsTimestampMacros(style.styleSt(Sid::oddHeaderR)) + || containsTimestampMacros(style.styleSt(Sid::evenHeaderL)) + || containsTimestampMacros(style.styleSt(Sid::evenHeaderC)) + || containsTimestampMacros(style.styleSt(Sid::evenHeaderR))) { + return true; + } + } + + if (style.styleB(Sid::showFooter)) { + if (containsTimestampMacros(style.styleSt(Sid::oddFooterL)) + || containsTimestampMacros(style.styleSt(Sid::oddFooterC)) + || containsTimestampMacros(style.styleSt(Sid::oddFooterR)) + || containsTimestampMacros(style.styleSt(Sid::evenFooterL)) + || containsTimestampMacros(style.styleSt(Sid::evenFooterC)) + || containsTimestampMacros(style.styleSt(Sid::evenFooterR))) { + return true; + } + } + + return false; +} diff --git a/src/engraving/rendering/score/headerfooterlayout.h b/src/engraving/rendering/score/headerfooterlayout.h index c38eec7b5f39a..2d7a7e85a8333 100644 --- a/src/engraving/rendering/score/headerfooterlayout.h +++ b/src/engraving/rendering/score/headerfooterlayout.h @@ -47,6 +47,8 @@ class HeaderFooterLayout /// How much the footer extends into the page (i.e., not in the margins) static double footerExtension(const LayoutContext& ctx, const Page* page); + static bool scoreHasTimestampHeadersFooters(const Score* score); + private: static void createUpdateHeaderText(LayoutContext& ctx, Page* page, int area, const String& s); static void createUpdateFooterText(LayoutContext& ctx, Page* page, int area, const String& s); @@ -61,5 +63,7 @@ class HeaderFooterLayout static CharFormat formatForMacro(const LayoutContext& ctx, const String& macro); static void appendFormattedString(std::list& fragments, const String& s, const CharFormat& defaultFormat, const CharFormat& macroFormat); + + static bool containsTimestampMacros(const String& text); }; } diff --git a/src/engraving/rendering/score/scorerenderer.cpp b/src/engraving/rendering/score/scorerenderer.cpp index c298f69a616e4..eb929f4a4662a 100644 --- a/src/engraving/rendering/score/scorerenderer.cpp +++ b/src/engraving/rendering/score/scorerenderer.cpp @@ -36,6 +36,7 @@ #include "scorepageviewlayout.h" #include "horizontalspacing.h" #include "slurtielayout.h" +#include "headerfooterlayout.h" #include "paint.h" @@ -130,3 +131,11 @@ void ScoreRenderer::layoutStem(Chord* item) LayoutContext ctx(item->score()); ChordLayout::layoutStem(item, ctx); } + +bool ScoreRenderer::scoreHasTimestampHeadersFooters(const Score* score) const +{ + if (score->layoutMode() != LayoutMode::PAGE && score->layoutMode() != LayoutMode::FLOAT) { + return false; + } + return HeaderFooterLayout::scoreHasTimestampHeadersFooters(score); +} diff --git a/src/engraving/rendering/score/scorerenderer.h b/src/engraving/rendering/score/scorerenderer.h index c8652d20601ff..c90e4e5909c18 100644 --- a/src/engraving/rendering/score/scorerenderer.h +++ b/src/engraving/rendering/score/scorerenderer.h @@ -53,6 +53,8 @@ class ScoreRenderer : public IScoreRenderer void computeBezier(TieSegment* tieSeg, PointF shoulderOffset = PointF()) override; void computeBezier(SlurSegment* slurSeg, PointF shoulderOffset = PointF()) override; + bool scoreHasTimestampHeadersFooters(const Score* score) const override; + private: // Layout Single Item void doLayoutItem(EngravingItem* item) override; diff --git a/src/project/internal/notationproject.cpp b/src/project/internal/notationproject.cpp index 08dda7cacaed3..46c99ca2cda17 100644 --- a/src/project/internal/notationproject.cpp +++ b/src/project/internal/notationproject.cpp @@ -521,15 +521,16 @@ Ret NotationProject::save(const muse::io::path_t& path, SaveMode saveMode, bool } // Re-compute headers and footers on save, to force timestamps update (if any) - const std::list& scores = m_engravingProject->masterScore()->scoreList(); - for (Score* s : scores) { - renderer()->layoutHeadersFooters(s); - } - for (const IExcerptNotationPtr& excerptNotation : m_masterNotation->excerpts()) { - if (excerptNotation->isInited() && excerptNotation->notation()->isOpen()) { - excerptNotation->notation()->notationChanged().notify(); + bool timestampsUpdated = false; + for (Score* s : m_engravingProject->masterScore()->scoreList()) { + if (renderer()->scoreHasTimestampHeadersFooters(s)) { + renderer()->layoutHeadersFooters(s); + timestampsUpdated = true; } } + if (timestampsUpdated) { + globalContext()->currentNotation()->notationChanged().notify(); + } } } break; case SaveMode::AutoSave: { diff --git a/src/project/internal/notationproject.h b/src/project/internal/notationproject.h index 99457fabca4e4..6daf0f0bd1476 100644 --- a/src/project/internal/notationproject.h +++ b/src/project/internal/notationproject.h @@ -39,6 +39,7 @@ #include "iprojectmigrator.h" #include "global/iglobalconfiguration.h" +#include "context/iglobalcontext.h" namespace mu::engraving { class MscReader; @@ -56,6 +57,7 @@ class NotationProject : public INotationProject, public muse::Contextable, publi muse::GlobalInject configuration; muse::GlobalInject globalConfiguration; muse::GlobalInject notationConfiguration; + muse::ContextInject globalContext = { this }; muse::ContextInject readers = { this }; muse::ContextInject writers = { this }; muse::ContextInject migrator = { this };