Skip to content
Closed
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
5 changes: 5 additions & 0 deletions src/engraving/dom/masterscore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,11 @@ void MasterScore::addExcerpt(Excerpt* ex, size_t index)
initParts(ex);
}

// Avoid adding duplicates
if (std::find(excerpts().begin(), excerpts().end(), ex) != excerpts().end()) {
return;
}

excerpts().insert(excerpts().begin() + (index == muse::nidx ? excerpts().size() : index), ex);
setExcerptsChanged(true);
}
Expand Down
15 changes: 15 additions & 0 deletions src/engraving/editing/editexcerpt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ void SwapExcerpt::flip(EditData*)
// ChangeExcerptTitle
//---------------------------------------------------------

ChangeExcerptTitle::ChangeExcerptTitle(Excerpt* x, const String& t)
: excerpt(x), title(t)
{
// Ensure excerpt is in master's list (required for saving to disk).
// "Potential" excerpts shown in Parts dialog are not in the list
// until explicitly created, so renaming them wouldn't persist.
MasterScore* master = excerpt->masterScore();
if (master) {
const std::vector<Excerpt*>& excerpts = master->excerpts();
if (std::find(excerpts.begin(), excerpts.end(), excerpt) == excerpts.end()) {
master->initAndAddExcerpt(excerpt, true);
}
}
}

void ChangeExcerptTitle::flip(EditData*)
{
String s = title;
Expand Down
3 changes: 1 addition & 2 deletions src/engraving/editing/editexcerpt.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,7 @@ class ChangeExcerptTitle : public UndoCommand
void flip(EditData*) override;

public:
ChangeExcerptTitle(Excerpt* x, const String& t)
: excerpt(x), title(t) {}
ChangeExcerptTitle(Excerpt* x, const String& t);

UNDO_TYPE(CommandType::ChangeExcerptTitle)
UNDO_NAME("ChangeExcerptTitle")
Expand Down
62 changes: 62 additions & 0 deletions src/engraving/tests/parts_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
#include "engraving/dom/segment.h"
#include "engraving/dom/spanner.h"
#include "engraving/dom/staff.h"
#include "engraving/editing/undo.h"
#include "engraving/editing/editexcerpt.h"

#include "utils/scorerw.h"
#include "utils/scorecomp.h"
Expand Down Expand Up @@ -1387,3 +1389,63 @@ TEST_F(Engraving_PartsTests, staffStyles)
}

#endif

//---------------------------------------------------------
// renamePotentialExcerpt
// Test that renaming a "potential" excerpt (not yet in
// master's excerpts list) persists after save/reload.
// This tests the fix for the bug where part names changed
// in the Parts dialog weren't saved if the part hadn't
// been opened as a tab.
//
// The fix: ChangeExcerptTitle auto-adds the excerpt to
// master's list if not present, ensuring it gets saved.
//---------------------------------------------------------

TEST_F(Engraving_PartsTests, renamePotentialExcerpt)
{
MasterScore* score = ScoreRW::readScore(PARTS_DATA_DIR + u"part-all.mscx");
ASSERT_TRUE(score);

// Create a "potential" excerpt from first part - NOT added to excerpts list yet
// This simulates what the Parts dialog does for parts that haven't been "generated"
std::vector<Part*> parts = { score->parts().at(0) };
std::vector<Excerpt*> potentialExcerpts = Excerpt::createExcerptsFromParts(parts, score);
ASSERT_EQ(potentialExcerpts.size(), 1u);
Excerpt* excerpt = potentialExcerpts.at(0);

// Verify excerpt is NOT in master's list (simulating a "potential" excerpt)
EXPECT_TRUE(std::find(score->excerpts().begin(), score->excerpts().end(), excerpt) == score->excerpts().end());

// Rename the excerpt - ChangeExcerptTitle should auto-add to master's list
const String newName = u"RenamedPart";
score->startCmd(TranslatableString::untranslatable("Test rename"));
score->undo(new ChangeExcerptTitle(excerpt, newName));
score->endCmd();

EXPECT_EQ(excerpt->name(), newName);
// Verify excerpt is now in master's list (auto-added by ChangeExcerptTitle)
EXPECT_TRUE(std::find(score->excerpts().begin(), score->excerpts().end(), excerpt) != score->excerpts().end());

// Save to temp file
String tempFile = PARTS_DATA_DIR + u"part-rename-potential-test.mscx";
EXPECT_TRUE(ScoreRW::saveScore(score, ScoreRW::rootPath() + u"/" + tempFile));
delete score;

// Reload and verify the name persisted
score = ScoreRW::readScore(tempFile);
ASSERT_TRUE(score);
ASSERT_FALSE(score->excerpts().empty()) << "No excerpts found - excerpt was not saved";

// Find the renamed excerpt
bool found = false;
for (Excerpt* ex : score->excerpts()) {
if (ex->name() == newName) {
found = true;
break;
}
}
EXPECT_TRUE(found) << "Renamed excerpt not found after reload";

delete score;
}
12 changes: 7 additions & 5 deletions src/notation/internal/excerptnotation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "excerptnotation.h"

#include "engraving/dom/excerpt.h"
#include "engraving/dom/masterscore.h"
#include "engraving/editing/editexcerpt.h"

using namespace mu::notation;
Expand Down Expand Up @@ -101,17 +102,18 @@ void ExcerptNotation::undoSetName(const QString& name)
return;
}

if (!score()) {
engraving::MasterScore* master = m_excerpt->masterScore();
if (!master) {
setName(name);
notifyAboutNotationChanged();
return;
}

//: Means: "edit the name of a part score"
undoStack()->prepareChanges(muse::TranslatableString("undoableAction", "Rename part"));

score()->undo(new engraving::ChangeExcerptTitle(m_excerpt, name));
master->startCmd(muse::TranslatableString("undoableAction", "Rename part"));
master->undo(new engraving::ChangeExcerptTitle(m_excerpt, name));
master->endCmd();

undoStack()->commitChanges();
notifyAboutNotationChanged();
}

Expand Down
Loading