Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/engraving/infrastructure/ifileinfoprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<IFileInfoProvider>;
Expand Down
5 changes: 5 additions & 0 deletions src/engraving/infrastructure/localfileinfoprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ DateTime LocalFileInfoProvider::lastModified() const
{
return fileSystem()->lastModified(m_path);
}

bool LocalFileInfoProvider::isNewlyCreated() const
{
return true;
}
1 change: 1 addition & 0 deletions src/engraving/infrastructure/localfileinfoprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/engraving/rendering/iscorerenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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;
Expand Down
48 changes: 42 additions & 6 deletions src/engraving/rendering/score/headerfooterlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Copy link
Copy Markdown
Contributor

@Jojo-Schmitz Jojo-Schmitz Jun 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with using the current time here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In these cases, the file has never been saved yet (so it technically does not exist) and we thought it would make more sense to display a placeholder to avoid confusion, since displaying the current time/date could lead to misinterpretation. If we ever support customising the time/date to something other than ISO, we could give this placeholder another think.

} 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");
Comment thread
ajuncosa marked this conversation as resolved.
} else {
newFragments.back().text += fileInfo->lastModified().date().toString(muse::DateFormat::ISODate);
}
Expand Down Expand Up @@ -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;
}
4 changes: 4 additions & 0 deletions src/engraving/rendering/score/headerfooterlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -61,5 +63,7 @@ class HeaderFooterLayout
static CharFormat formatForMacro(const LayoutContext& ctx, const String& macro);
static void appendFormattedString(std::list<TextFragment>& fragments, const String& s, const CharFormat& defaultFormat,
const CharFormat& macroFormat);

static bool containsTimestampMacros(const String& text);
};
}
23 changes: 20 additions & 3 deletions src/engraving/rendering/score/scorepageviewlayout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
3 changes: 3 additions & 0 deletions src/engraving/rendering/score/scorepageviewlayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -38,6 +39,8 @@ class ScorePageViewLayout

static void doLayout(LayoutContext& ctx);

static void doLayoutHeadersFooters(LayoutContext& ctx);

static void layoutFinished(Score* score, LayoutContext& ctx);
};
}
Expand Down
20 changes: 20 additions & 0 deletions src/engraving/rendering/score/scorerenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
3 changes: 3 additions & 0 deletions src/engraving/rendering/score/scorerenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
13 changes: 13 additions & 0 deletions src/project/internal/notationproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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: {
Expand Down
4 changes: 4 additions & 0 deletions src/project/internal/notationproject.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -55,9 +57,11 @@ class NotationProject : public INotationProject, public muse::Contextable, publi
muse::GlobalInject<IProjectConfiguration> configuration;
muse::GlobalInject<muse::IGlobalConfiguration> globalConfiguration;
muse::GlobalInject<notation::INotationConfiguration> notationConfiguration;
muse::ContextInject<context::IGlobalContext> globalContext = { this };
muse::ContextInject<INotationReadersRegister> readers = { this };
muse::ContextInject<INotationWritersRegister> writers = { this };
muse::ContextInject<IProjectMigrator> migrator = { this };
muse::ContextInject<engraving::rendering::IScoreRenderer> renderer = { this };

public:
NotationProject(const muse::modularity::ContextPtr& iocCtx)
Expand Down
5 changes: 5 additions & 0 deletions src/project/internal/projectfileinfoprovider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,8 @@ DateTime ProjectFileInfoProvider::lastModified() const
{
return filesystem()->lastModified(path());
}

bool ProjectFileInfoProvider::isNewlyCreated() const
{
return m_project->isNewlyCreated();
}
1 change: 1 addition & 0 deletions src/project/internal/projectfileinfoprovider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading