diff --git a/src/importexport/mei/imeiconfiguration.h b/src/importexport/mei/imeiconfiguration.h index 82b81fa702dde..ef9a213509a3b 100644 --- a/src/importexport/mei/imeiconfiguration.h +++ b/src/importexport/mei/imeiconfiguration.h @@ -37,6 +37,9 @@ class IMeiConfiguration : MODULE_EXPORT_INTERFACE virtual bool meiExportLayout() const = 0; virtual void setMeiExportLayout(bool value) = 0; + + virtual bool meiUseMuseScoreIds() const = 0; + virtual void setMeiUseMuseScoreIds(bool value) = 0; }; } diff --git a/src/importexport/mei/internal/meiconfiguration.cpp b/src/importexport/mei/internal/meiconfiguration.cpp index f6ec8a7c10a04..b6d67980efbf8 100644 --- a/src/importexport/mei/internal/meiconfiguration.cpp +++ b/src/importexport/mei/internal/meiconfiguration.cpp @@ -31,11 +31,13 @@ static const std::string module_name("iex_mei"); static const Settings::Key MEI_IMPORT_LAYOUT_KEY(module_name, "import/mei/importMeiLayout"); static const Settings::Key MEI_EXPORT_LAYOUT_KEY(module_name, "export/mei/exportMeiLayout"); +static const Settings::Key MEI_USE_MUSESCORE_IDS_KEY(module_name, "export/mei/useMuseScoreIds"); void MeiConfiguration::init() { settings()->setDefaultValue(MEI_IMPORT_LAYOUT_KEY, Val(true)); settings()->setDefaultValue(MEI_EXPORT_LAYOUT_KEY, Val(true)); + settings()->setDefaultValue(MEI_USE_MUSESCORE_IDS_KEY, Val(false)); } bool MeiConfiguration::meiImportLayout() const @@ -57,3 +59,13 @@ void MeiConfiguration::setMeiExportLayout(bool value) { settings()->setSharedValue(MEI_EXPORT_LAYOUT_KEY, Val(value)); } + +bool MeiConfiguration::meiUseMuseScoreIds() const +{ + return settings()->value(MEI_USE_MUSESCORE_IDS_KEY).toBool(); +} + +void MeiConfiguration::setMeiUseMuseScoreIds(bool value) +{ + settings()->setSharedValue(MEI_USE_MUSESCORE_IDS_KEY, Val(value)); +} diff --git a/src/importexport/mei/internal/meiconfiguration.h b/src/importexport/mei/internal/meiconfiguration.h index ecda19989d380..6b507ebdc0f69 100644 --- a/src/importexport/mei/internal/meiconfiguration.h +++ b/src/importexport/mei/internal/meiconfiguration.h @@ -35,6 +35,9 @@ class MeiConfiguration : public IMeiConfiguration bool meiExportLayout() const override; void setMeiExportLayout(bool value) override; + + bool meiUseMuseScoreIds() const override; + void setMeiUseMuseScoreIds(bool value) override; }; } diff --git a/src/importexport/mei/internal/meiexporter.cpp b/src/importexport/mei/internal/meiexporter.cpp index 4a5a1e9887652..873e16400b6c5 100644 --- a/src/importexport/mei/internal/meiexporter.cpp +++ b/src/importexport/mei/internal/meiexporter.cpp @@ -46,6 +46,7 @@ #include "engraving/dom/laissezvib.h" #include "engraving/dom/lyrics.h" #include "engraving/dom/marker.h" +#include "engraving/dom/masterscore.h" #include "engraving/dom/measure.h" #include "engraving/dom/measurerepeat.h" #include "engraving/dom/note.h" @@ -93,6 +94,8 @@ using namespace mu::engraving; bool MeiExporter::write(std::string& meiData) { + const bool useMuseScoreIds = configuration()->meiUseMuseScoreIds(); + m_uids = UIDRegister::instance(); m_xmlIDCounter = 0; @@ -129,10 +132,24 @@ bool MeiExporter::write(std::string& meiData) m_mei = meiDoc.append_child("mei"); m_mei.append_attribute("xmlns") = "http://www.music-encoding.org/ns/mei"; - // Save xml:id metaTag's as mei@xml:id - String xmlId = m_score->metaTag(u"xml:id"); - if (!xmlId.isEmpty()) { - m_mei.append_attribute("xml:id") = xmlId.toStdString().c_str(); + // Option to use MuseScore Ids has priority + if (useMuseScoreIds) { + std::stringstream xmlId; + EID eid = m_score->masterScore()->eid(); + if (!eid.isValid()) { + eid = m_score->masterScore()->assignNewEID(); + } + String eidStr = String::fromStdString(eid.toStdString().c_str()); + xmlId << "mscore-" << eidStr.replace('/', '.').replace('+', '-').toStdString(); + m_mei.append_attribute("xml:id") = xmlId.str().c_str(); + } + // Otherwise check if we have a metaTag + else { + // Save xml:id metaTag's as mei@xml:id + String xmlId = m_score->metaTag(u"xml:id"); + if (!xmlId.isEmpty()) { + m_mei.append_attribute("xml:id") = xmlId.toStdString().c_str(); + } } libmei::AttConverter converter; @@ -2539,7 +2556,16 @@ std::string MeiExporter::generateHashID() std::string MeiExporter::getXmlIdFor(const EngravingItem* item, const char c) { - if (m_uids->hasUid(item)) { + const bool useMuseScoreIds = configuration()->meiUseMuseScoreIds(); + + if (useMuseScoreIds) { + EID eid = item->eid(); + if (!eid.isValid()) { + eid = item->assignNewEID(); + } + String eidStr = String::fromStdString(eid.toStdString().c_str()); + return "mscore-" + eidStr.replace('/', '.').replace('+', '-').toStdString(); + } else if (m_uids->hasUid(item)) { return m_uids->uid(item); } diff --git a/src/importexport/mei/internal/meiimporter.cpp b/src/importexport/mei/internal/meiimporter.cpp index b2e5d49cc987c..25c6877aea9cd 100644 --- a/src/importexport/mei/internal/meiimporter.cpp +++ b/src/importexport/mei/internal/meiimporter.cpp @@ -98,6 +98,7 @@ bool MeiImporter::read(const muse::io::path_t& path) { m_uids = UIDRegister::instance(); m_uids->clear(); + m_hasMuseScoreIds = false; m_lastMeasure = nullptr; m_tremoloId.clear(); @@ -134,12 +135,30 @@ bool MeiImporter::read(const muse::io::path_t& path) success = success && this->readMeiHead(root); - success = success && this->readScore(root); - pugi::xml_attribute xmlId = root.attribute("xml:id"); + bool hasRootXmlId = false; if (xmlId && !String(xmlId.value()).empty()) { - m_score->setMetaTag(u"xml:id", String(xmlId.value())); - // Do not keep a xml:id map when having a xml:id seed. + hasRootXmlId = true; + String xmlIdStr = String(xmlId.value()); + if (xmlIdStr.startsWith(u"mscore-")) { + // Keep a global flag since we are going to read them only if mei@xml:id is given with mscore EID + m_hasMuseScoreIds = true; + String valStr = xmlIdStr.remove(u"mscore-").replace('.', '/').replace('-', '+'); + // The mei@xml:id store the score EID + EID eid = EID::fromStdString(valStr.toStdString()); + if (eid.isValid()) { + m_score->setEID(eid); + } + } else { + // Keep it as a seed + m_score->setMetaTag(u"xml:id", xmlIdStr); + } + } + + success = success && this->readScore(root); + + if (hasRootXmlId) { + // Do not keep a xml:id map when having a xml:id seed or MscoreIds m_uids->clear(); } @@ -260,7 +279,11 @@ ChordRest* MeiImporter::addChordRest(pugi::xml_node node, Measure* measure, int } else { chordRest = Factory::createChord(segment); } - m_uids->reg(chordRest, meiElement.m_xmlId); + + // Do not use single note xml:id / EID for the ChordRest + if (!dynamic_cast(&meiElement)) { + this->readXmlId(chordRest, meiElement.m_xmlId); + } if (m_startIdChordRests.count(meiElement.m_xmlId)) { m_startIdChordRests[meiElement.m_xmlId] = chordRest; @@ -458,7 +481,7 @@ EngravingItem* MeiImporter::addAnnotation(const libmei::Element& meiElement, Mea } else { return nullptr; } - m_uids->reg(item, meiElement.m_xmlId); + this->readXmlId(item, meiElement.m_xmlId); item->setTrack(chordRest->track()); segment->add(item); @@ -504,7 +527,7 @@ Spanner* MeiImporter::addSpanner(const libmei::Element& meiElement, Measure* mea } else { return nullptr; } - m_uids->reg(item, meiElement.m_xmlId); + this->readXmlId(item, meiElement.m_xmlId); item->setTick(chordRest->tick()); item->setStartElement(chordRest); @@ -549,7 +572,7 @@ EngravingItem* MeiImporter::addToChordRest(const libmei::Element& meiElement, Me } else { return nullptr; } - m_uids->reg(item, meiElement.m_xmlId); + this->readXmlId(item, meiElement.m_xmlId); item->setTrack(chordRest->track()); chordRest->add(item); @@ -856,6 +879,23 @@ void MeiImporter::setOrnamentAccid(engraving::Ornament* ornament, const Convert: } } +void MeiImporter::readXmlId(engraving::EngravingItem* item, const std::string& meiUID) +{ + String xmlIdStr = String::fromStdString(meiUID); + // We have a file that has MuseScore EIDs and one on this element + if (m_hasMuseScoreIds && xmlIdStr.startsWith(u"mscore-")) { + String valStr = xmlIdStr.remove(u"mscore-").replace('.', '/').replace('-', '+'); + EID eid = EID::fromStdString(valStr.toStdString()); + if (!eid.isValid()) { + Convert::logs.push_back(String("A valid MuseScore ID could not be extracted from '%1'").arg(xmlIdStr)); + } else { + item->setEID(eid); + } + } else { + m_uids->reg(item, meiUID); + } +} + //--------------------------------------------------------- // parsing methods //--------------------------------------------------------- @@ -1323,7 +1363,7 @@ bool MeiImporter::readEnding(pugi::xml_node endingNode) } else { Volta* volta = Factory::createVolta(m_score->dummy()); Convert::endingFromMEI(volta, meiEnding, warning); - m_uids->reg(volta, meiEnding.m_xmlId); + this->readXmlId(volta, meiEnding.m_xmlId); volta->setTrack(0); volta->setTrack2(0); volta->setTick(m_endingStart->tick()); @@ -1356,7 +1396,7 @@ bool MeiImporter::readMeasure(pugi::xml_node measureNode) Convert::MeasureStruct measureSt = Convert::measureFromMEI(meiMeasure, warning); Measure* measure = Factory::createMeasure(m_score->dummy()->system()); - m_uids->reg(measure, meiMeasure.m_xmlId); + this->readXmlId(measure, meiMeasure.m_xmlId); measure->setTick(m_ticks); measure->setTimesig(m_currentTimeSig); @@ -1730,7 +1770,7 @@ bool MeiImporter::readChord(pugi::xml_node chordNode, Measure* measure, int trac NOT_SUPPORTED; } else { TremoloSingleChord* tremolo = Factory::createTremoloSingleChord(chord); - m_uids->reg(tremolo, m_tremoloId); + this->readXmlId(tremolo, m_tremoloId); tremolo->setTremoloType(ttype); chord->add(tremolo); } @@ -1761,7 +1801,7 @@ bool MeiImporter::readClef(pugi::xml_node clefNode, Measure* measure, int track, Segment* segment = measure->getSegment(SegmentType::Clef, ticks + measure->tick()); Clef* clef = Factory::createClef(segment); Convert::colorFromMEI(clef, meiClef); - m_uids->reg(clef, meiClef.m_xmlId); + this->readXmlId(clef, meiClef.m_xmlId); clef->setClefType(ClefTypeList(Convert::clefFromMEI(meiClef, warning))); if (warning) { this->addLog("clef", clefNode); @@ -1828,7 +1868,7 @@ bool MeiImporter::readMRest(pugi::xml_node mRestNode, Measure* measure, int trac Segment* segment = measure->getSegment(SegmentType::ChordRest, ticks + measure->tick()); Rest* rest = Factory::createRest(segment, TDuration(DurationType::V_MEASURE)); Convert::colorFromMEI(rest, meiMRest); - m_uids->reg(rest, meiMRest.m_xmlId); + this->readXmlId(rest, meiMRest.m_xmlId); rest->setTicks(m_currentTimeSig); rest->setDurationType(DurationType::V_MEASURE); rest->setTrack(track); @@ -1868,7 +1908,7 @@ bool MeiImporter::readMRpt(pugi::xml_node mRptNode, Measure* measure, int track, Segment* segment = measure->getSegment(SegmentType::ChordRest, ticks + measure->tick()); MeasureRepeat* measureRepeat = Factory::createMeasureRepeat(segment); Convert::colorFromMEI(measureRepeat, meiMRpt); - m_uids->reg(measureRepeat, meiMRpt.m_xmlId); + this->readXmlId(measureRepeat, meiMRpt.m_xmlId); measureRepeat->setTrack(track); measureRepeat->setTicks(measure->ticks()); measureRepeat->setNumMeasures(1); @@ -1904,6 +1944,8 @@ bool MeiImporter::readNote(pugi::xml_node noteNode, Measure* measure, int track, } else { // Support for non MEI-Basic accid and accid.ges encoded in - this is not academic... meiAccid.Read(noteNode); + // Remove the xml:id read from the note in that case + meiAccid.m_xmlId = ""; } Staff* staff = m_score->staff(track2staff(track)); @@ -1926,7 +1968,7 @@ bool MeiImporter::readNote(pugi::xml_node noteNode, Measure* measure, int track, NOT_SUPPORTED; } else { TremoloSingleChord* tremolo = Factory::createTremoloSingleChord(chord); - m_uids->reg(tremolo, m_tremoloId); + this->readXmlId(tremolo, m_tremoloId); tremolo->setTremoloType(ttype); chord->add(tremolo); } @@ -1935,7 +1977,7 @@ bool MeiImporter::readNote(pugi::xml_node noteNode, Measure* measure, int track, Note* note = Factory::createNote(chord); Convert::colorFromMEI(note, meiNote); - m_uids->reg(note, meiNote.m_xmlId); + this->readXmlId(note, meiNote.m_xmlId); // If there is a reference to the note in the MEI, add it the maps (e.g., for ties) if (m_startIdChordRests.count(meiNote.m_xmlId)) { @@ -1950,7 +1992,7 @@ bool MeiImporter::readNote(pugi::xml_node noteNode, Measure* measure, int track, Accidental* accid = Factory::createAccidental(note); Convert::colorFromMEI(accid, meiAccid); - m_uids->reg(accid, meiAccid.m_xmlId); + this->readXmlId(accid, meiAccid.m_xmlId); accid->setAccidentalType(pitchSt.accidType); //accid->setBracket(AccidentalBracket::BRACKET); // Not supported in MEI-Basic accid->setRole(pitchSt.accidRole); @@ -2046,7 +2088,7 @@ bool MeiImporter::readTuplet(pugi::xml_node tupletNode, Measure* measure, int tr meiTuplet.Read(tupletNode); m_tuplet = Factory::createTuplet(measure); - m_uids->reg(m_tuplet, meiTuplet.m_xmlId); + this->readXmlId(m_tuplet, meiTuplet.m_xmlId); Convert::tupletFromMEI(m_tuplet, meiTuplet, warning); if (warning) { this->addLog("tuplet", tupletNode); @@ -2123,7 +2165,7 @@ bool MeiImporter::readVerse(pugi::xml_node verseNode, Chord* chord) } Lyrics* lyrics = Factory::createLyrics(chord); - m_uids->reg(lyrics, meiVerse.m_xmlId); + this->readXmlId(lyrics, meiVerse.m_xmlId); Convert::colorFromMEI(lyrics, meiVerse); bool success = true; @@ -2402,7 +2444,7 @@ bool MeiImporter::readF(pugi::xml_node fNode, engraving::FiguredBass* figuredBas const int line = static_cast(figuredBass->itemsCount()); FiguredBassItem* figuredBassItem = figuredBass->createItem(line); - m_uids->reg(figuredBassItem, meiF.m_xmlId); + this->readXmlId(figuredBassItem, meiF.m_xmlId); figuredBassItem->setTrack(figuredBass->track()); figuredBassItem->setParent(figuredBass); @@ -2444,7 +2486,7 @@ bool MeiImporter::readFb(pugi::xml_node harmNode, Measure* measure) return true; } // Needs to be registered by hand because we pass meiHarm to MeiImporter::addAnnotation - m_uids->reg(figuredBass, meiFb.m_xmlId); + this->readXmlId(figuredBass, meiFb.m_xmlId); Convert::fbFromMEI(figuredBass, meiHarm, meiFb, warning); @@ -2478,7 +2520,7 @@ bool MeiImporter::readFermata(pugi::xml_node fermataNode, Measure* measure) if (fermataPos == measure->ticks()) { Segment* segment = measure->getSegment(SegmentType::EndBarLine, measure->tick() + measure->ticks()); fermata = Factory::createFermata(segment); - m_uids->reg(fermata, meiFermata.m_xmlId); + this->readXmlId(fermata, meiFermata.m_xmlId); const int staffIdx = (meiFermata.HasStaff() && meiFermata.GetStaff().size() > 0) ? this->getStaffIndex(meiFermata.GetStaff().at(0)) : 0; fermata->setTrack(staffIdx * VOICES); @@ -2791,7 +2833,7 @@ bool MeiImporter::readRepeatMark(pugi::xml_node repeatMarkNode, Measure* measure item = Factory::createMarker(measure); Convert::markerFromMEI(dynamic_cast(item), meiRepeatMark, warning); } - m_uids->reg(item, meiRepeatMark.m_xmlId); + this->readXmlId(item, meiRepeatMark.m_xmlId); item->setTrack(0); measure->add(item); @@ -2874,7 +2916,7 @@ bool MeiImporter::readTie(pugi::xml_node tieNode, Measure* measure) } Tie* tie = new Tie(m_score->dummy()); - m_uids->reg(tie, meiTie.m_xmlId); + this->readXmlId(tie, meiTie.m_xmlId); startNote->setTieFor(tie); tie->setStartNote(startNote); tie->setTrack(startNote->track()); diff --git a/src/importexport/mei/internal/meiimporter.h b/src/importexport/mei/internal/meiimporter.h index c9a43bcf90bb0..72254a03b5f80 100644 --- a/src/importexport/mei/internal/meiimporter.h +++ b/src/importexport/mei/internal/meiimporter.h @@ -198,12 +198,18 @@ class MeiImporter void extendLyrics(); void setOrnamentAccid(engraving::Ornament* ornament, const Convert::OrnamStruct& ornamSt); + /** Read the xmlId and process it appropriately */ + void readXmlId(engraving::EngravingItem* item, const std::string& meiUID); + /** The Score pointer */ engraving::Score* m_score = nullptr; /** The uid register */ UIDRegister* m_uids; + /** A flag indicating the file has MuseScore EIDs as xml:ids */ + bool m_hasMuseScoreIds; + engraving::Fraction m_ticks; int m_lastMeasureN; engraving::Measure* m_lastMeasure; diff --git a/src/project/qml/MuseScore/Project/internal/Export/MeiSettingsPage.qml b/src/project/qml/MuseScore/Project/internal/Export/MeiSettingsPage.qml index 470d0ce9eec4e..8ea3335445b8e 100644 --- a/src/project/qml/MuseScore/Project/internal/Export/MeiSettingsPage.qml +++ b/src/project/qml/MuseScore/Project/internal/Export/MeiSettingsPage.qml @@ -39,4 +39,18 @@ ExportSettingsPage { root.model.meiExportLayout = !checked } } + + CheckBox { + width: parent.width + text: qsTrc("project/export", "Use MuseScore element IDs") + + navigation.name: "MeiUseMuseScoreIds" + navigation.panel: root.navigationPanel + navigation.row: root.navigationOrder + 1 + + checked: root.model.meiUseMuseScoreIds + onClicked: { + root.model.meiUseMuseScoreIds = !checked + } + } } diff --git a/src/project/view/exportdialogmodel.cpp b/src/project/view/exportdialogmodel.cpp index b3a918b034d56..2670fd75cfe1d 100644 --- a/src/project/view/exportdialogmodel.cpp +++ b/src/project/view/exportdialogmodel.cpp @@ -553,6 +553,21 @@ void ExportDialogModel::setMeiExportLayout(bool exportLayout) emit meiExportLayoutChanged(exportLayout); } +bool ExportDialogModel::meiUseMuseScoreIds() const +{ + return meiConfiguration()->meiUseMuseScoreIds(); +} + +void ExportDialogModel::setMeiUseMuseScoreIds(bool useMuseScoreIds) +{ + if (useMuseScoreIds == meiUseMuseScoreIds()) { + return; + } + + meiConfiguration()->setMeiUseMuseScoreIds(useMuseScoreIds); + emit meiUseMuseScoreIdsChanged(useMuseScoreIds); +} + QVariantList ExportDialogModel::musicXmlLayoutTypes() const { QMap musicXmlLayoutTypeNames { diff --git a/src/project/view/exportdialogmodel.h b/src/project/view/exportdialogmodel.h index 13724d7bdef76..2128348c47442 100644 --- a/src/project/view/exportdialogmodel.h +++ b/src/project/view/exportdialogmodel.h @@ -86,6 +86,7 @@ class ExportDialogModel : public QAbstractListModel, public muse::async::Asyncab Q_PROPERTY(MusicXmlLayoutType musicXmlLayoutType READ musicXmlLayoutType WRITE setMusicXmlLayoutType NOTIFY musicXmlLayoutTypeChanged) Q_PROPERTY(int meiExportLayout READ meiExportLayout WRITE setMeiExportLayout NOTIFY meiExportLayoutChanged) + Q_PROPERTY(int meiUseMuseScoreIds READ meiUseMuseScoreIds WRITE setMeiUseMuseScoreIds NOTIFY meiUseMuseScoreIdsChanged) Q_PROPERTY(bool shouldDestinationFolderBeOpenedOnExport READ shouldDestinationFolderBeOpenedOnExport WRITE setShouldDestinationFolderBeOpenedOnExport NOTIFY shouldDestinationFolderBeOpenedOnExportChanged) @@ -149,6 +150,9 @@ class ExportDialogModel : public QAbstractListModel, public muse::async::Asyncab bool meiExportLayout() const; void setMeiExportLayout(bool exportLayout); + bool meiUseMuseScoreIds() const; + void setMeiUseMuseScoreIds(bool useMuseScoreIds); + enum class MusicXmlLayoutType { AllLayout, AllBreaks, @@ -191,6 +195,7 @@ class ExportDialogModel : public QAbstractListModel, public muse::async::Asyncab void musicXmlLayoutTypeChanged(MusicXmlLayoutType layoutType); void meiExportLayoutChanged(bool exportLayout); + void meiUseMuseScoreIdsChanged(bool useMuseScoreIds); void shouldDestinationFolderBeOpenedOnExportChanged(bool shouldDestinationFolderBeOpenedOnExport);