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/iscorerenderer.h b/src/engraving/rendering/iscorerenderer.h index e02a6bd0d3e21..e0e4d5cda5ffe 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 { @@ -199,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 1d1270ff8eff6..68077a70257a2 100644 --- a/src/engraving/rendering/score/headerfooterlayout.cpp +++ b/src/engraving/rendering/score/headerfooterlayout.cpp @@ -322,18 +322,17 @@ 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); + if (fileInfo->isNewlyCreated()) { + newFragments.back().text += String(u"HH:mm:ss"); } 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); + if (fileInfo->isNewlyCreated()) { + newFragments.back().text += String(u"YYYY-MM-DD"); } else { newFragments.back().text += fileInfo->lastModified().date().toString(muse::DateFormat::ISODate); } @@ -489,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/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..eb929f4a4662a 100644 --- a/src/engraving/rendering/score/scorerenderer.cpp +++ b/src/engraving/rendering/score/scorerenderer.cpp @@ -33,8 +33,10 @@ #include "chordlayout.h" #include "layoutcontext.h" #include "scorelayout.h" +#include "scorepageviewlayout.h" #include "horizontalspacing.h" #include "slurtielayout.h" +#include "headerfooterlayout.h" #include "paint.h" @@ -49,6 +51,16 @@ void ScoreRenderer::layoutScore(Score* score, const Fraction& st, const Fraction ScoreLayout::layoutRange(score, st, et); } +void ScoreRenderer::layoutHeadersFooters(Score* score) const +{ + 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 { return Paint::pageSizeInch(score); @@ -119,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 9392e3aacd2c1..c90e4e5909c18 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; @@ -52,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 e3413ef927fda..46c99ca2cda17 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" @@ -518,6 +519,18 @@ 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) + 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 155a7ba8beacf..6daf0f0bd1476 100644 --- a/src/project/internal/notationproject.h +++ b/src/project/internal/notationproject.h @@ -32,12 +32,14 @@ #include "inotationwritersregister.h" #include "engraving/engravingproject.h" +#include "engraving/rendering/iscorerenderer.h" #include "notation/inotationconfiguration.h" #include "projectaudiosettings.h" #include "iprojectmigrator.h" #include "global/iglobalconfiguration.h" +#include "context/iglobalcontext.h" namespace mu::engraving { class MscReader; @@ -55,9 +57,11 @@ 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 }; + muse::ContextInject renderer = { this }; public: NotationProject(const muse::modularity::ContextPtr& iocCtx) 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;