From 443df7f2abd5926632cd7581d6eee5e20e937cce Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 10 Dec 2025 10:10:40 +0000 Subject: [PATCH 01/11] Make lyric lines editable and add thickness property --- src/engraving/dom/line.cpp | 8 -- src/engraving/dom/line.h | 2 +- src/engraving/dom/lyrics.cpp | 6 ++ src/engraving/dom/lyrics.h | 18 +++-- src/engraving/dom/lyricsline.cpp | 73 ++++++++++++++--- src/engraving/dom/trill.cpp | 11 +++ src/engraving/dom/trill.h | 3 + .../rendering/score/lyricslayout.cpp | 5 +- .../internal/elementrepositoryservice.cpp | 12 +-- .../qml/MuseScore/Inspector/CMakeLists.txt | 3 + .../Inspector/abstractinspectormodel.cpp | 6 +- .../Inspector/abstractinspectormodel.h | 2 + .../Inspector/inspectormodelcreator.cpp | 5 ++ .../NotationInspectorSectionLoader.qml | 11 +++ .../notation/lines/LyricsLineSettings.qml | 79 +++++++++++++++++++ .../lines/lyricslinesettingsmodel.cpp | 78 ++++++++++++++++++ .../notation/lines/lyricslinesettingsmodel.h | 61 ++++++++++++++ .../notation/lyrics/LyricsSettings.qml | 2 +- 18 files changed, 343 insertions(+), 42 deletions(-) create mode 100644 src/inspector/qml/MuseScore/Inspector/notation/lines/LyricsLineSettings.qml create mode 100644 src/inspector/qml/MuseScore/Inspector/notation/lines/lyricslinesettingsmodel.cpp create mode 100644 src/inspector/qml/MuseScore/Inspector/notation/lines/lyricslinesettingsmodel.h diff --git a/src/engraving/dom/line.cpp b/src/engraving/dom/line.cpp index 24ff525cd63f4..0142585cb6757 100644 --- a/src/engraving/dom/line.cpp +++ b/src/engraving/dom/line.cpp @@ -596,14 +596,6 @@ void LineSegment::rebaseAnchors(EditData& ed, Grip grip) return; } - if (isTrillSegment()) { - EngravingItem* startElement = spanner()->startElement(); - if (startElement && startElement->isChord() && toChord(startElement)->staffMove() != 0) { - // This trill is on a cross-staff chord. Don't try to rebase its anchors when dragging. - return; - } - } - // don't change anchors on keyboard adjustment or if Ctrl is pressed // (Ctrl+Left/Right is handled elsewhere!) if (ed.key == Key_Left || ed.key == Key_Right || ed.key == Key_Up || ed.key == Key_Down || ed.modifiers & ControlModifier) { diff --git a/src/engraving/dom/line.h b/src/engraving/dom/line.h index dd3fed71ead0f..d4a282b7a7e95 100644 --- a/src/engraving/dom/line.h +++ b/src/engraving/dom/line.h @@ -79,6 +79,7 @@ class LineSegment : public SpannerSegment protected: virtual void rebaseOffsetsOnAnchorChanged(Grip grip, const PointF& oldPos, System* sys); + virtual void rebaseAnchors(EditData&, Grip); private: void undoMoveStartEndAndSnappedItems(EditData& ed, bool moveStart, bool moveEnd, Segment* s1, Segment* s2); @@ -90,7 +91,6 @@ class LineSegment : public SpannerSegment static PointF deltaRebaseRight(const Segment* oldSeg, const Segment* newSeg); static Fraction lastSegmentEndTick(const Segment* lastSeg, const Spanner* s); LineSegment* rebaseAnchor(Grip grip, Segment* newSeg); - void rebaseAnchors(EditData&, Grip); }; //--------------------------------------------------------- diff --git a/src/engraving/dom/lyrics.cpp b/src/engraving/dom/lyrics.cpp index fb73bae549d0f..5c8d3c9f5c404 100644 --- a/src/engraving/dom/lyrics.cpp +++ b/src/engraving/dom/lyrics.cpp @@ -591,4 +591,10 @@ void Lyrics::removeInvalidSegments() } } } + +void mu::engraving::PartialLyricsLine::setIsEndMelisma(bool val) +{ + m_isEndMelisma = val; + styleChanged(); +} } diff --git a/src/engraving/dom/lyrics.h b/src/engraving/dom/lyrics.h index 68a8cc03ae39a..c095e7c07f4dc 100644 --- a/src/engraving/dom/lyrics.h +++ b/src/engraving/dom/lyrics.h @@ -104,8 +104,6 @@ class Lyrics final : public TextBase bool avoidBarlines() const { return m_avoidBarlines; } void setAvoidBarlines(bool v) { m_avoidBarlines = v; } -protected: - private: friend class Factory; @@ -140,7 +138,6 @@ class LyricsLine : public SLine LineSegment* createLineSegment(System* parent) override; void removeUnmanaged() override; - void styleChanged() override; virtual Lyrics* lyrics() const { return toLyrics(explicitParent()); } Lyrics* nextLyrics() const { return m_nextLyrics; } @@ -148,6 +145,8 @@ class LyricsLine : public SLine virtual bool isEndMelisma() const { return lyrics() && lyrics()->ticks().isNotZero(); } bool isDash() const { return !isEndMelisma(); } bool setProperty(Pid propertyId, const PropertyValue& v) override; + PropertyValue propertyDefault(Pid id) const override; + Sid getPropertyStyle(Pid) const override; protected: LyricsLine(const ElementType& type, EngravingItem* parent, ElementFlags = ElementFlag::NOTHING); @@ -182,10 +181,12 @@ class LyricsLineSegment : public LineSegment virtual bool lyricsAddToSkyline() const { return lyrics()->addToSkyline(); } virtual double lineSpacing() const { return lyrics()->lineSpacing(); } Color color() const override { return lyrics()->color(); } - int gripsCount() const override { return 0; } - Grip initialEditModeGrip() const override { return Grip::NO_GRIP; } - Grip defaultGrip() const override { return Grip::NO_GRIP; } - bool needStartEditingAfterSelecting() const override { return false; } + + PropertyValue getProperty(Pid propertyId) const override; + bool setProperty(Pid propertyId, const PropertyValue&) override; + PropertyValue propertyDefault(Pid propertyId) const override; + + bool allowTimeAnchor() const override { return false; } struct LayoutData : public LineSegment::LayoutData { public: @@ -199,6 +200,7 @@ class LyricsLineSegment : public LineSegment protected: LyricsLineSegment(const ElementType& type, LyricsLine* sp, System* parent, ElementFlags f = ElementFlag::NOTHING); + void rebaseAnchors(EditData&, Grip) override; }; class PartialLyricsLine final : public LyricsLine @@ -214,7 +216,7 @@ class PartialLyricsLine final : public LyricsLine Lyrics* lyrics() const override { return nullptr; } - void setIsEndMelisma(bool val) { m_isEndMelisma = val; } + void setIsEndMelisma(bool val); bool isEndMelisma() const override { return m_isEndMelisma; } void setVerse(int val) { m_verse = val; } diff --git a/src/engraving/dom/lyricsline.cpp b/src/engraving/dom/lyricsline.cpp index d5ea2252bc455..4686a4a400bc5 100644 --- a/src/engraving/dom/lyricsline.cpp +++ b/src/engraving/dom/lyricsline.cpp @@ -40,12 +40,16 @@ namespace mu::engraving { // LyricsLine //--------------------------------------------------------- +static const ElementStyle lyricsLineElementStyle { + { Sid::lyricsDashLineThickness, Pid::LINE_WIDTH } +}; + LyricsLine::LyricsLine(EngravingItem* parent) - : SLine(ElementType::LYRICSLINE, parent, ElementFlag::NOT_SELECTABLE) + : SLine(ElementType::LYRICSLINE, parent) { setGenerated(true); // no need to save it, as it can be re-generated setDiagonal(false); - setLineWidth(style().styleS(Sid::lyricsDashLineThickness)); + initElementStyle(&lyricsLineElementStyle); setAnchor(Spanner::Anchor::SEGMENT); m_nextLyrics = 0; } @@ -55,7 +59,7 @@ LyricsLine::LyricsLine(const ElementType& type, EngravingItem* parent, ElementFl { setGenerated(true); // no need to save it, as it can be re-generated setDiagonal(false); - setLineWidth(style().styleS(Sid::lyricsDashLineThickness)); + initElementStyle(&lyricsLineElementStyle); setAnchor(Spanner::Anchor::SEGMENT); m_nextLyrics = 0; } @@ -66,15 +70,6 @@ LyricsLine::LyricsLine(const LyricsLine& g) m_nextLyrics = 0; } -//--------------------------------------------------------- -// styleChanged -//--------------------------------------------------------- - -void LyricsLine::styleChanged() -{ - setLineWidth(style().styleS(Sid::lyricsDashLineThickness)); -} - //--------------------------------------------------------- // createLineSegment //--------------------------------------------------------- @@ -128,6 +123,25 @@ bool LyricsLine::setProperty(Pid propertyId, const engraving::PropertyValue& v) return true; } +PropertyValue LyricsLine::propertyDefault(Pid id) const +{ + switch (id) { + case Pid::LINE_WIDTH: + return styleValue(Pid::LINE_WIDTH, getPropertyStyle(Pid::LINE_WIDTH)); + default: + return SLine::propertyDefault(id); + } +} + +Sid LyricsLine::getPropertyStyle(Pid propertyId) const +{ + if (propertyId == Pid::LINE_WIDTH) { + return isEndMelisma() ? Sid::lyricsLineThickness : Sid::lyricsDashLineThickness; + } + + return SLine::getPropertyStyle(propertyId); +} + void LyricsLine::doComputeEndElement() { if (!isEndMelisma()) { @@ -146,8 +160,14 @@ void LyricsLine::doComputeEndElement() // LyricsLineSegment //========================================================= +void LyricsLineSegment::rebaseAnchors(EditData&, Grip) +{ + // Don't rebase lyric line anchors on drag + return; +} + LyricsLineSegment::LyricsLineSegment(LyricsLine* sp, System* parent) - : LineSegment(ElementType::LYRICSLINE_SEGMENT, sp, parent, ElementFlag::ON_STAFF | ElementFlag::NOT_SELECTABLE) + : LineSegment(ElementType::LYRICSLINE_SEGMENT, sp, parent, ElementFlag::ON_STAFF) { setGenerated(true); } @@ -168,6 +188,33 @@ double LyricsLineSegment::baseLineShift() const return -style().styleD(Sid::lyricsDashYposRatio) * segLyrics->fontMetrics().xHeight(); } +PropertyValue LyricsLineSegment::getProperty(Pid propertyId) const +{ + if (EngravingObject* delegate = propertyDelegate(propertyId)) { + return delegate->getProperty(propertyId); + } + + return LineSegment::getProperty(propertyId); +} + +bool LyricsLineSegment::setProperty(Pid propertyId, const PropertyValue& val) +{ + if (EngravingObject* delegate = propertyDelegate(propertyId)) { + return delegate->setProperty(propertyId, val); + } + + return LineSegment::setProperty(propertyId, val); +} + +PropertyValue LyricsLineSegment::propertyDefault(Pid propertyId) const +{ + if (EngravingObject* delegate = propertyDelegate(propertyId)) { + return delegate->propertyDefault(propertyId); + } + + return LineSegment::propertyDefault(propertyId); +} + //========================================================= // PartialLyricsLine //========================================================= diff --git a/src/engraving/dom/trill.cpp b/src/engraving/dom/trill.cpp index a7e61fc098bd5..4d3582b31a6e1 100644 --- a/src/engraving/dom/trill.cpp +++ b/src/engraving/dom/trill.cpp @@ -121,6 +121,17 @@ void TrillSegment::symbolLine(SymId start, SymId fill, SymId end) setbbox(r); } +void TrillSegment::rebaseAnchors(EditData& ed, Grip grip) +{ + EngravingItem* startElement = spanner()->startElement(); + if (startElement && startElement->isChord() && toChord(startElement)->staffMove() != 0) { + // This trill is on a cross-staff chord. Don't try to rebase its anchors when dragging. + return; + } + + LineSegment::rebaseAnchors(ed, grip); +} + //--------------------------------------------------------- // scanElements //--------------------------------------------------------- diff --git a/src/engraving/dom/trill.h b/src/engraving/dom/trill.h index 4b22f1d2f59c0..e762fc5b18961 100644 --- a/src/engraving/dom/trill.h +++ b/src/engraving/dom/trill.h @@ -58,6 +58,9 @@ class TrillSegment final : public LineSegment void symbolLine(SymId start, SymId fill); void symbolLine(SymId start, SymId fill, SymId end); +protected: + void rebaseAnchors(EditData& ed, Grip grip) override; + private: Sid getPropertyStyle(Pid) const override; diff --git a/src/engraving/rendering/score/lyricslayout.cpp b/src/engraving/rendering/score/lyricslayout.cpp index 45f8942477165..c0cb2dd06eb73 100644 --- a/src/engraving/rendering/score/lyricslayout.cpp +++ b/src/engraving/rendering/score/lyricslayout.cpp @@ -199,9 +199,7 @@ void LyricsLayout::layout(Lyrics* item, LayoutContext& ctx) void LyricsLayout::layout(LyricsLine* item, LayoutContext& ctx) { - if (item->isEndMelisma()) { // melisma - item->setLineWidth(ctx.conf().styleS(Sid::lyricsLineThickness)); - } else { // dash(es) + if (item->isDash()) { // dash(es) item->setNextLyrics(searchNextLyrics(item->lyrics()->segment(), item->staffIdx(), item->lyrics()->verse(), @@ -529,6 +527,7 @@ void LyricsLayout::createOrRemoveLyricsLine(Lyrics* item, LayoutContext& ctx) item->separator()->setTrack(item->track()); item->separator()->setTrack2(item->track()); item->separator()->setVisible(item->visible()); + item->separator()->styleChanged(); } else { if (item->separator()) { item->separator()->removeUnmanaged(); diff --git a/src/inspector/internal/elementrepositoryservice.cpp b/src/inspector/internal/elementrepositoryservice.cpp index e836b9b3a4387..fdb75774fc05e 100644 --- a/src/inspector/internal/elementrepositoryservice.cpp +++ b/src/inspector/internal/elementrepositoryservice.cpp @@ -104,8 +104,10 @@ QList ElementRepositoryService::findElementsByTyp case mu::engraving::ElementType::LAISSEZ_VIB: case mu::engraving::ElementType::PARTIAL_TIE: case mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE: - case mu::engraving::ElementType::PALM_MUTE: + case mu::engraving::ElementType::LYRICSLINE: + case mu::engraving::ElementType::PARTIAL_LYRICSLINE: case mu::engraving::ElementType::WHAMMY_BAR: + case mu::engraving::ElementType::PALM_MUTE: return findLines(elementType); default: QList resultList; @@ -326,7 +328,9 @@ QList ElementRepositoryService::findLines(mu::eng { mu::engraving::ElementType::TIE, mu::engraving::ElementType::TIE_SEGMENT }, { mu::engraving::ElementType::LAISSEZ_VIB, mu::engraving::ElementType::LAISSEZ_VIB_SEGMENT }, { mu::engraving::ElementType::PARTIAL_TIE, mu::engraving::ElementType::PARTIAL_TIE_SEGMENT }, - { mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE, mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT } + { mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE, mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT }, + { mu::engraving::ElementType::LYRICSLINE, mu::engraving::ElementType::LYRICSLINE_SEGMENT }, + { mu::engraving::ElementType::PARTIAL_LYRICSLINE, mu::engraving::ElementType::PARTIAL_LYRICSLINE_SEGMENT } }; QList resultList; @@ -491,10 +495,6 @@ QList ElementRepositoryService::findLyrics() const for (mu::engraving::EngravingItem* element : m_exposedElementList) { if (element->isLyrics()) { resultList << element; - } else if (element->isPartialLyricsLine()) { - resultList << element; - } else if (element->isPartialLyricsLineSegment()) { - resultList << toPartialLyricsLineSegment(element)->lyricsLine(); } } diff --git a/src/inspector/qml/MuseScore/Inspector/CMakeLists.txt b/src/inspector/qml/MuseScore/Inspector/CMakeLists.txt index d6ce1842f302e..0607503639f90 100644 --- a/src/inspector/qml/MuseScore/Inspector/CMakeLists.txt +++ b/src/inspector/qml/MuseScore/Inspector/CMakeLists.txt @@ -134,6 +134,8 @@ qt_add_qml_module(inspector_qml notation/lines/vibratosettingsmodel.h notation/lines/voltasettingsmodel.cpp notation/lines/voltasettingsmodel.h + notation/lines/lyricslinesettingsmodel.cpp + notation/lines/lyricslinesettingsmodel.h notation/lyrics/lyricssettingsmodel.cpp notation/lyrics/lyricssettingsmodel.h notation/markers/markersettingsmodel.cpp @@ -321,6 +323,7 @@ qt_add_qml_module(inspector_qml notation/lines/LineSettings.qml notation/lines/SlurAndTieSettings.qml notation/lines/VibratoSettings.qml + notation/lines/LyricsLineSettings.qml notation/lyrics/LyricsSettings.qml notation/markers/MarkerSettings.qml notation/measurerepeats/MeasureRepeatSettings.qml diff --git a/src/inspector/qml/MuseScore/Inspector/abstractinspectormodel.cpp b/src/inspector/qml/MuseScore/Inspector/abstractinspectormodel.cpp index d3ed07007cba6..c6ebb33c5220e 100644 --- a/src/inspector/qml/MuseScore/Inspector/abstractinspectormodel.cpp +++ b/src/inspector/qml/MuseScore/Inspector/abstractinspectormodel.cpp @@ -121,8 +121,10 @@ static const QMap NOTATION_ELEME { mu::engraving::ElementType::GRADUAL_TEMPO_CHANGE_SEGMENT, InspectorModelType::TYPE_GRADUAL_TEMPO_CHANGE }, { mu::engraving::ElementType::INSTRUMENT_NAME, InspectorModelType::TYPE_INSTRUMENT_NAME }, { mu::engraving::ElementType::LYRICS, InspectorModelType::TYPE_LYRICS }, - { mu::engraving::ElementType::PARTIAL_LYRICSLINE, InspectorModelType::TYPE_LYRICS }, - { mu::engraving::ElementType::PARTIAL_LYRICSLINE_SEGMENT, InspectorModelType::TYPE_LYRICS }, + { mu::engraving::ElementType::LYRICSLINE, InspectorModelType::TYPE_LYRICS_LINE }, + { mu::engraving::ElementType::LYRICSLINE_SEGMENT, InspectorModelType::TYPE_LYRICS_LINE }, + { mu::engraving::ElementType::PARTIAL_LYRICSLINE, InspectorModelType::TYPE_PARTIAL_LYRICS_LINE }, + { mu::engraving::ElementType::PARTIAL_LYRICSLINE_SEGMENT, InspectorModelType::TYPE_PARTIAL_LYRICS_LINE }, { mu::engraving::ElementType::REST, InspectorModelType::TYPE_REST }, { mu::engraving::ElementType::DYNAMIC, InspectorModelType::TYPE_DYNAMIC }, { mu::engraving::ElementType::EXPRESSION, InspectorModelType::TYPE_EXPRESSION }, diff --git a/src/inspector/qml/MuseScore/Inspector/abstractinspectormodel.h b/src/inspector/qml/MuseScore/Inspector/abstractinspectormodel.h index 6315e1cd89622..453726d468cef 100644 --- a/src/inspector/qml/MuseScore/Inspector/abstractinspectormodel.h +++ b/src/inspector/qml/MuseScore/Inspector/abstractinspectormodel.h @@ -146,6 +146,8 @@ class AbstractInspectorModel : public QObject, public muse::async::Asyncable, pu TYPE_GRADUAL_TEMPO_CHANGE, TYPE_INSTRUMENT_NAME, TYPE_LYRICS, + TYPE_LYRICS_LINE, + TYPE_PARTIAL_LYRICS_LINE, TYPE_REST, TYPE_REST_BEAM, TYPE_REST_REST, diff --git a/src/inspector/qml/MuseScore/Inspector/inspectormodelcreator.cpp b/src/inspector/qml/MuseScore/Inspector/inspectormodelcreator.cpp index a15f2bbca743e..3ecde3f616dc9 100644 --- a/src/inspector/qml/MuseScore/Inspector/inspectormodelcreator.cpp +++ b/src/inspector/qml/MuseScore/Inspector/inspectormodelcreator.cpp @@ -70,6 +70,7 @@ #include "notation/tuplets/tupletsettingsmodel.h" #include "notation/instrumentname/instrumentnamesettingsmodel.h" #include "notation/lyrics/lyricssettingsmodel.h" +#include "notation/lines/lyricslinesettingsmodel.h" #include "notation/rests/beams/restbeamsettingsmodel.h" #include "notation/rests/restsettingsmodel.h" #include "notation/rests/restsettingsproxymodel.h" @@ -201,6 +202,10 @@ AbstractInspectorModel* InspectorModelCreator::newInspectorModel(InspectorModelT return new InstrumentNameSettingsModel(parent, repository); case InspectorModelType::TYPE_LYRICS: return new LyricsSettingsModel(parent, repository); + case InspectorModelType::TYPE_LYRICS_LINE: + return new LyricsLineSettingsModel(parent, repository, LyricsLineSettingsModel::LyricsLine); + case InspectorModelType::TYPE_PARTIAL_LYRICS_LINE: + return new LyricsLineSettingsModel(parent, repository, LyricsLineSettingsModel::PartialLyricsLine); case InspectorModelType::TYPE_REST: return new RestSettingsProxyModel(parent, repository); case InspectorModelType::TYPE_REST_BEAM: diff --git a/src/inspector/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml b/src/inspector/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml index 37d95c9cfbe58..224ffeaf826dd 100644 --- a/src/inspector/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml +++ b/src/inspector/qml/MuseScore/Inspector/notation/NotationInspectorSectionLoader.qml @@ -141,6 +141,8 @@ Loader { case AbstractInspectorModel.TYPE_TUPLET: return tupletComp case AbstractInspectorModel.TYPE_INSTRUMENT_NAME: return instrumentNameComp case AbstractInspectorModel.TYPE_LYRICS: return lyricsComp + case AbstractInspectorModel.TYPE_LYRICS_LINE: return lyricsLineComp + case AbstractInspectorModel.TYPE_PARTIAL_LYRICS_LINE: return lyricsLineComp case AbstractInspectorModel.TYPE_REST: return restComp case AbstractInspectorModel.TYPE_REST_BEAM: return restComp case AbstractInspectorModel.TYPE_DYNAMIC: return dynamicComp @@ -516,6 +518,15 @@ Loader { } } + Component { + id: lyricsLineComp + LyricsLineSettings { + model: root.model as LyricsLineSettingsModel + navigationPanel: root.navigationPanel + navigationRowStart: root.navigationRowStart + } + } + Component { id: restComp RestSettings { diff --git a/src/inspector/qml/MuseScore/Inspector/notation/lines/LyricsLineSettings.qml b/src/inspector/qml/MuseScore/Inspector/notation/lines/LyricsLineSettings.qml new file mode 100644 index 0000000000000..8d7cf3e1e2c9b --- /dev/null +++ b/src/inspector/qml/MuseScore/Inspector/notation/lines/LyricsLineSettings.qml @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import QtQuick + +import Muse.Ui +import Muse.UiComponents +import MuseScore.Inspector + +import "../../common" +import "internal" + +Column { + id: root + + required property LyricsLineSettingsModel model + + property NavigationPanel navigationPanel: null + property int navigationRowStart: 1 + + objectName: "LyricsLineSettings" + + spacing: 12 + + function focusOnFirst() { + thicknessSection.focusOnFirst() + } + + SpinBoxPropertyView { + id: thicknessSection + + titleText: qsTrc("inspector", "Thickness") + propertyItem: model ? model.thickness : null + + step: 0.01 + maxValue: 10.00 + minValue: 0.01 + decimals: 2 + + navigationName: "Thickness" + navigationPanel: root.navigationPanel + navigationRowStart: root.navigationRowStart + } + + SpinBoxPropertyView { + id: setVerse + titleText: qsTrc("inspector", "Set to verse") + propertyItem: root.model ? root.model.verse : null + + visible: model ? model.hasVerse : false + + decimals: 0 + step: 1 + minValue: 1 + + navigationName: "Verse" + navigationPanel: root.navigationPanel + navigationRowStart: thicknessSection.navigationRowEnd + 1 + } + +} diff --git a/src/inspector/qml/MuseScore/Inspector/notation/lines/lyricslinesettingsmodel.cpp b/src/inspector/qml/MuseScore/Inspector/notation/lines/lyricslinesettingsmodel.cpp new file mode 100644 index 0000000000000..090b70d89b7b3 --- /dev/null +++ b/src/inspector/qml/MuseScore/Inspector/notation/lines/lyricslinesettingsmodel.cpp @@ -0,0 +1,78 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "lyricslinesettingsmodel.h" + +#include "translation.h" + +using namespace mu::inspector; + +LyricsLineSettingsModel::LyricsLineSettingsModel(QObject* parent, IElementRepositoryService* repository, + ElementType elementType) + : AbstractInspectorModel(parent, repository) +{ + if (elementType == ElementType::LyricsLine) { + setTitle(muse::qtrc("inspector", "Lyrics line")); + setElementType(mu::engraving::ElementType::LYRICSLINE); + setModelType(InspectorModelType::TYPE_LYRICS_LINE); + } else if (elementType == ElementType::PartialLyricsLine) { + setTitle(muse::qtrc("inspector", "Partial lyrics line")); + setElementType(mu::engraving::ElementType::PARTIAL_LYRICSLINE); + setModelType(InspectorModelType::TYPE_PARTIAL_LYRICS_LINE); + m_hasVerse = true; + } + setIcon(muse::ui::IconCode::Code::LYRICS); + + createProperties(); +} + +PropertyItem* LyricsLineSettingsModel::thickness() const +{ + return m_thickness; +} + +PropertyItem* LyricsLineSettingsModel::verse() const +{ + return m_verse; +} + +bool LyricsLineSettingsModel::hasVerse() const +{ + return m_hasVerse; +} + +void LyricsLineSettingsModel::createProperties() +{ + m_thickness = buildPropertyItem(mu::engraving::Pid::LINE_WIDTH); + m_verse = buildPropertyItem(mu::engraving::Pid::VERSE); +} + +void LyricsLineSettingsModel::loadProperties() +{ + loadPropertyItem(m_thickness); + loadPropertyItem(m_verse); +} + +void LyricsLineSettingsModel::resetProperties() +{ + m_thickness->resetToDefault(); + m_verse->resetToDefault(); +} diff --git a/src/inspector/qml/MuseScore/Inspector/notation/lines/lyricslinesettingsmodel.h b/src/inspector/qml/MuseScore/Inspector/notation/lines/lyricslinesettingsmodel.h new file mode 100644 index 0000000000000..619c8c2b402a8 --- /dev/null +++ b/src/inspector/qml/MuseScore/Inspector/notation/lines/lyricslinesettingsmodel.h @@ -0,0 +1,61 @@ +/* + * SPDX-License-Identifier: GPL-3.0-only + * MuseScore-Studio-CLA-applies + * + * MuseScore Studio + * Music Composition & Notation + * + * Copyright (C) 2025 MuseScore Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once + +#include + +#include "abstractinspectormodel.h" + +namespace mu::inspector { +class LyricsLineSettingsModel : public AbstractInspectorModel +{ + Q_OBJECT + QML_ELEMENT; + QML_UNCREATABLE("Not creatable from QML") + + Q_PROPERTY(mu::inspector::PropertyItem * thickness READ thickness CONSTANT) + Q_PROPERTY(mu::inspector::PropertyItem * verse READ verse CONSTANT) + + Q_PROPERTY(bool hasVerse READ hasVerse CONSTANT) + +public: + enum ElementType { + LyricsLine, + PartialLyricsLine + }; + explicit LyricsLineSettingsModel(QObject* parent, IElementRepositoryService* repository, ElementType elementType); + + PropertyItem* thickness() const; + PropertyItem* verse() const; + bool hasVerse() const; + +private: + void createProperties() override; + void loadProperties() override; + void resetProperties() override; + + PropertyItem* m_thickness = nullptr; + PropertyItem* m_verse = nullptr; + + bool m_hasVerse = false; +}; +} diff --git a/src/inspector/qml/MuseScore/Inspector/notation/lyrics/LyricsSettings.qml b/src/inspector/qml/MuseScore/Inspector/notation/lyrics/LyricsSettings.qml index 04cd85a469ddd..9c9ea8105cc22 100644 --- a/src/inspector/qml/MuseScore/Inspector/notation/lyrics/LyricsSettings.qml +++ b/src/inspector/qml/MuseScore/Inspector/notation/lyrics/LyricsSettings.qml @@ -52,7 +52,7 @@ Column { step: 1 minValue: 1 - navigationName: "SpanFrom" + navigationName: "Verse" navigationPanel: root.navigationPanel navigationRowStart: root.navigationRowStart } From 1edb0acae38cc95bd5310ad1deb8ce37d2a927ab Mon Sep 17 00:00:00 2001 From: James Mizen Date: Thu, 11 Dec 2025 15:27:44 +0000 Subject: [PATCH 02/11] Allow editing lyric line offset --- src/engraving/dom/lyricsline.cpp | 2 ++ .../rendering/score/lyricslayout.cpp | 28 ++++++++----------- src/engraving/rendering/score/lyricslayout.h | 2 +- src/engraving/rendering/score/tlayout.cpp | 6 ++-- src/engraving/rendering/score/tlayout.h | 2 +- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/engraving/dom/lyricsline.cpp b/src/engraving/dom/lyricsline.cpp index 4686a4a400bc5..b9fed4c0627e3 100644 --- a/src/engraving/dom/lyricsline.cpp +++ b/src/engraving/dom/lyricsline.cpp @@ -128,6 +128,8 @@ PropertyValue LyricsLine::propertyDefault(Pid id) const switch (id) { case Pid::LINE_WIDTH: return styleValue(Pid::LINE_WIDTH, getPropertyStyle(Pid::LINE_WIDTH)); + case Pid::OFFSET: + return lyrics() ? lyrics()->offset() : SLine::propertyDefault(id); default: return SLine::propertyDefault(id); } diff --git a/src/engraving/rendering/score/lyricslayout.cpp b/src/engraving/rendering/score/lyricslayout.cpp index c0cb2dd06eb73..c48d7a46366f2 100644 --- a/src/engraving/rendering/score/lyricslayout.cpp +++ b/src/engraving/rendering/score/lyricslayout.cpp @@ -197,7 +197,7 @@ void LyricsLayout::layout(Lyrics* item, LayoutContext& ctx) } } -void LyricsLayout::layout(LyricsLine* item, LayoutContext& ctx) +void LyricsLayout::layout(LyricsLine* item) { if (item->isDash()) { // dash(es) item->setNextLyrics(searchNextLyrics(item->lyrics()->segment(), @@ -237,10 +237,6 @@ void LyricsLayout::layout(LyricsLineSegment* item, LayoutContext& ctx) assert(item->isPartialLyricsLineSegment() || item->lyrics()); - if (!item->isPartialLyricsLineSegment()) { - item->ryoffset() = 0.0; - } - LyricsLineSegment::LayoutData* ldata = item->mutldata(); ldata->clearDashes(); @@ -284,9 +280,7 @@ void LyricsLayout::layoutMelismaLine(LyricsLineSegment* item) adjustLyricsLineYOffset(item); - double y = 0.0; // actual value is set later - - item->setPos(startX, y); + item->mutldata()->setPosX(startX); item->setPos2(PointF(endX - startX, 0.0)); item->mutldata()->addDash(LineF(PointF(), item->pos2())); @@ -334,9 +328,7 @@ void LyricsLayout::layoutDashes(LyricsLineSegment* item) adjustLyricsLineYOffset(item, endLyrics); - double y = 0.0; // actual value is set later - - item->setPos(startX, y); + item->mutldata()->setPosX(startX); item->setPos2(PointF(endX - startX, 0.0)); bool isDashOnFirstSyllable = lyricsLine->tick2() == system->firstMeasure()->tick(); @@ -365,7 +357,7 @@ void LyricsLayout::layoutDashes(LyricsLineSegment* item) startX -= 0.5 * diff; endX += 0.5 * diff; } - item->setPos(startX, y); + item->mutldata()->setPosX(startX); item->setPos2(PointF(endX - startX, 0.0)); curLength = endX - startX; } @@ -871,23 +863,27 @@ void LyricsLayout::adjustLyricsLineYOffset(LyricsLineSegment* item, const Lyrics const Lyrics* startLyrics = lyricsLine->lyrics(); const bool melisma = lyricsLine->isEndMelisma(); + LyricsLineSegment::LayoutData* ldata = item->mutldata(); + // Partial melisma or dashes if (lyricsLine->isPartialLyricsLine()) { Lyrics* nextLyrics = findNextLyrics(endChordRest, item->verse()); - item->ryoffset() = nextLyrics ? nextLyrics->offset().y() : item->offset().y(); + if (nextLyrics) { + ldata->setPosY(nextLyrics->offset().y()); + } return; } if (item->isSingleBeginType()) { - item->ryoffset() = startLyrics->offset().y(); + ldata->setPosY(startLyrics->offset().y()); return; } if (melisma || !endLyrics) { Lyrics* nextLyrics = findNextLyrics(endChordRest, item->verse()); - item->ryoffset() = nextLyrics ? nextLyrics->offset().y() : startLyrics->offset().y(); + ldata->setPosY(nextLyrics ? nextLyrics->offset().y() : startLyrics->offset().y()); return; } - item->ryoffset() = endLyrics->offset().y(); + ldata->setPosY(endLyrics->offset().y()); } diff --git a/src/engraving/rendering/score/lyricslayout.h b/src/engraving/rendering/score/lyricslayout.h index 88575719c1f21..52af5ba747f19 100644 --- a/src/engraving/rendering/score/lyricslayout.h +++ b/src/engraving/rendering/score/lyricslayout.h @@ -54,7 +54,7 @@ class LyricsLayout LyricsLayout() = default; static void layout(Lyrics* item, LayoutContext& ctx); - static void layout(LyricsLine* item, LayoutContext& ctx); + static void layout(LyricsLine* item); static void layout(LyricsLineSegment* item, LayoutContext& ctx); static void computeVerticalPositions(System* system, LayoutContext& ctx); diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index 1f1fd47ceefc2..a2fd055ae7d07 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -3788,10 +3788,10 @@ void TLayout::layoutLyrics(Lyrics* item, LayoutContext& ctx) LyricsLayout::layout(item, ctx); } -void TLayout::layoutLyricsLine(LyricsLine* item, LayoutContext& ctx) +void TLayout::layoutLyricsLine(LyricsLine* item) { LAYOUT_CALL_ITEM(item); - LyricsLayout::layout(item, ctx); + LyricsLayout::layout(item); } void TLayout::layoutLyricsLineSegment(LyricsLineSegment* item, LayoutContext& ctx) @@ -6693,7 +6693,7 @@ SpannerSegment* TLayout::layoutSystem(LyricsLine* line, System* system, LayoutCo SpannerSegmentType sst; if (line->tick() >= stick) { - TLayout::layoutLyricsLine(line, ctx); + TLayout::layoutLyricsLine(line); if (line->ticks().isZero() && line->isEndMelisma()) { return nullptr; } diff --git a/src/engraving/rendering/score/tlayout.h b/src/engraving/rendering/score/tlayout.h index 6ff7f2b1cb445..3822d0b189dec 100644 --- a/src/engraving/rendering/score/tlayout.h +++ b/src/engraving/rendering/score/tlayout.h @@ -276,7 +276,7 @@ class TLayout static void layoutLetRingSegment(LetRingSegment* item, LayoutContext& ctx); static void layoutLineSegment(LineSegment* item, LayoutContext& ctx); // factory static void layoutLyrics(Lyrics* item, LayoutContext& ctx); - static void layoutLyricsLine(LyricsLine* item, LayoutContext& ctx); + static void layoutLyricsLine(LyricsLine* item); static void layoutLyricsLineSegment(LyricsLineSegment* item, LayoutContext& ctx); static void layoutMarker(Marker* item, Marker::LayoutData* ldata, LayoutContext& ctx); From c3aa593d94938aa4b5bf6d363a2a9e6d06660b7a Mon Sep 17 00:00:00 2001 From: James Mizen Date: Thu, 11 Dec 2025 16:11:06 +0000 Subject: [PATCH 03/11] Allow editing lyric dash line length --- src/engraving/rendering/score/lyricslayout.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engraving/rendering/score/lyricslayout.cpp b/src/engraving/rendering/score/lyricslayout.cpp index c48d7a46366f2..32b4afc32a968 100644 --- a/src/engraving/rendering/score/lyricslayout.cpp +++ b/src/engraving/rendering/score/lyricslayout.cpp @@ -332,7 +332,9 @@ void LyricsLayout::layoutDashes(LyricsLineSegment* item) item->setPos2(PointF(endX - startX, 0.0)); bool isDashOnFirstSyllable = lyricsLine->tick2() == system->firstMeasure()->tick(); - double curLength = endX - startX; + const double userStart = startX + item->offset().x(); + const double userEnd = endX + item->userOff2().x(); + double curLength = userEnd - userStart; double dashMinLength = style.styleMM(Sid::lyricsDashMinLength); bool firstAndLastGapAreHalf = style.styleB(Sid::lyricsDashFirstAndLastGapAreHalf); bool forceDash = style.styleB(Sid::lyricsDashForce) From 3e68a044ae795c22bc6e31310d95a55aa84ec956 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Thu, 11 Dec 2025 16:44:21 +0000 Subject: [PATCH 04/11] Read/write lyric lines --- src/engraving/dom/factory.cpp | 5 ++- src/engraving/dom/factory.h | 3 ++ src/engraving/dom/lyricsline.cpp | 5 +-- src/engraving/dom/utils.cpp | 29 ++++++++++++++++++ src/engraving/dom/utils.h | 9 ++++-- .../rendering/score/lyricslayout.cpp | 29 ------------------ src/engraving/rw/read460/read460.cpp | 12 ++++++++ src/engraving/rw/read460/tread.cpp | 20 +++++++++++- src/engraving/rw/read460/tread.h | 2 ++ src/engraving/rw/write/twrite.cpp | 17 +++++++++- src/engraving/rw/write/twrite.h | 1 + vtest/scores/lyrics-31.mscz | Bin 0 -> 25456 bytes 12 files changed, 95 insertions(+), 37 deletions(-) create mode 100644 vtest/scores/lyrics-31.mscz diff --git a/src/engraving/dom/factory.cpp b/src/engraving/dom/factory.cpp index 8975781befc04..c1385fedf858a 100644 --- a/src/engraving/dom/factory.cpp +++ b/src/engraving/dom/factory.cpp @@ -222,6 +222,7 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent) case ElementType::GUITAR_BEND: return new GuitarBend(parent->isNote() ? toNote(parent) : dummy->note()); case ElementType::TREMOLOBAR: return new TremoloBar(parent); case ElementType::LYRICS: return new Lyrics(parent->isChordRest() ? toChordRest(parent) : dummy->chord()); + case ElementType::LYRICSLINE: return new LyricsLine(parent); case ElementType::FIGURED_BASS: return new FiguredBass(parent->isSegment() ? toSegment(parent) : dummy->segment()); case ElementType::STEM: return new Stem(parent->isChord() ? toChord(parent) : dummy->chord()); case ElementType::SLUR: return new Slur(parent); @@ -247,7 +248,6 @@ EngravingItem* Factory::doCreateItem(ElementType type, EngravingItem* parent) case ElementType::PARTIAL_LYRICSLINE: return new PartialLyricsLine(parent); case ElementType::PARENTHESIS: return new Parenthesis(parent); - case ElementType::LYRICSLINE: case ElementType::TEXTLINE_BASE: case ElementType::TEXTLINE_SEGMENT: case ElementType::GLISSANDO_SEGMENT: @@ -446,6 +446,9 @@ MAKE_ITEM_IMPL(LayoutBreak, MeasureBase) CREATE_ITEM_IMPL(Lyrics, ElementType::LYRICS, ChordRest, isAccessibleEnabled) COPY_ITEM_IMPL(Lyrics) +CREATE_ITEM_IMPL(LyricsLine, ElementType::LYRICSLINE, EngravingItem, isAccessibleEnabled) +COPY_ITEM_IMPL(LyricsLine) + CREATE_ITEM_IMPL(Measure, ElementType::MEASURE, System, isAccessibleEnabled) COPY_ITEM_IMPL(Measure) diff --git a/src/engraving/dom/factory.h b/src/engraving/dom/factory.h index 9522b87112bb8..ad553d1e74647 100644 --- a/src/engraving/dom/factory.h +++ b/src/engraving/dom/factory.h @@ -129,6 +129,9 @@ class Factory static Lyrics* createLyrics(ChordRest* parent, bool isAccessibleEnabled = true); static Lyrics* copyLyrics(const Lyrics& src); + static LyricsLine* createLyricsLine(EngravingItem* parent, bool isAccessibleEnabled = true); + static LyricsLine* copyLyricsLine(const LyricsLine& src); + static Measure* createMeasure(System* parent, bool isAccessibleEnabled = true); static Measure* copyMeasure(const Measure& src); diff --git a/src/engraving/dom/lyricsline.cpp b/src/engraving/dom/lyricsline.cpp index b9fed4c0627e3..1c642b1412f5b 100644 --- a/src/engraving/dom/lyricsline.cpp +++ b/src/engraving/dom/lyricsline.cpp @@ -47,21 +47,21 @@ static const ElementStyle lyricsLineElementStyle { LyricsLine::LyricsLine(EngravingItem* parent) : SLine(ElementType::LYRICSLINE, parent) { - setGenerated(true); // no need to save it, as it can be re-generated setDiagonal(false); initElementStyle(&lyricsLineElementStyle); setAnchor(Spanner::Anchor::SEGMENT); m_nextLyrics = 0; + setGenerated(true); // no need to save it, as it can be re-generated } LyricsLine::LyricsLine(const ElementType& type, EngravingItem* parent, ElementFlags f) : SLine(type, parent, f) { - setGenerated(true); // no need to save it, as it can be re-generated setDiagonal(false); initElementStyle(&lyricsLineElementStyle); setAnchor(Spanner::Anchor::SEGMENT); m_nextLyrics = 0; + setGenerated(true); // no need to save it, as it can be re-generated } LyricsLine::LyricsLine(const LyricsLine& g) @@ -119,6 +119,7 @@ bool LyricsLine::setProperty(Pid propertyId, const engraving::PropertyValue& v) } break; } + setGenerated(false); triggerLayout(); return true; } diff --git a/src/engraving/dom/utils.cpp b/src/engraving/dom/utils.cpp index 8579c101297a2..bf649c0c0e8e4 100644 --- a/src/engraving/dom/utils.cpp +++ b/src/engraving/dom/utils.cpp @@ -1866,4 +1866,33 @@ std::vector filterTargetElements(const Selection& sel, Engraving } return result; } + +Lyrics* searchNextLyrics(Segment* s, staff_idx_t staffIdx, int verse, PlacementV p) +{ + Lyrics* l = nullptr; + const Segment* originalSeg = s; + while ((s = s->next1(SegmentType::ChordRest))) { + if (!segmentsAreAdjacentInRepeatStructure(originalSeg, s)) { + return nullptr; + } + + track_idx_t strack = staffIdx * VOICES; + track_idx_t etrack = strack + VOICES; + // search through all tracks of current staff looking for a lyric in specified verse + for (track_idx_t track = strack; track < etrack; ++track) { + ChordRest* cr = toChordRest(s->element(track)); + if (cr) { + // cr with lyrics found, but does it have a syllable in specified verse? + l = cr->lyrics(verse, p); + if (l) { + break; + } + } + } + if (l) { + break; + } + } + return l; +} } diff --git a/src/engraving/dom/utils.h b/src/engraving/dom/utils.h index 218f7067eb34c..3d7d001021a55 100644 --- a/src/engraving/dom/utils.h +++ b/src/engraving/dom/utils.h @@ -29,20 +29,21 @@ #include "draw/types/geometry.h" namespace mu::engraving { -class Score; class Chord; class ChordRest; class EngravingItem; class KeySig; +class Lyrics; +class Measure; class Note; class Rest; -class Measure; +class Score; class Score; class Segment; class Selection; class Spanner; -class System; class Staff; +class System; class Tuplet; class Volta; struct NoteVal; @@ -126,4 +127,6 @@ extern bool isValidBarLineForRepeatSection(const Segment* firstSeg, const Segmen extern bool isElementInFretBox(const EngravingItem* item); extern std::vector filterTargetElements(const Selection& sel, EngravingItem* dropElement, bool& unique); + +extern Lyrics* searchNextLyrics(Segment* s, staff_idx_t staffIdx, int verse, PlacementV p); } // namespace mu::engraving diff --git a/src/engraving/rendering/score/lyricslayout.cpp b/src/engraving/rendering/score/lyricslayout.cpp index 32b4afc32a968..c7dd3b306e309 100644 --- a/src/engraving/rendering/score/lyricslayout.cpp +++ b/src/engraving/rendering/score/lyricslayout.cpp @@ -41,35 +41,6 @@ using namespace mu; using namespace mu::engraving; using namespace mu::engraving::rendering::score; -static Lyrics* searchNextLyrics(Segment* s, staff_idx_t staffIdx, int verse, PlacementV p) -{ - Lyrics* l = nullptr; - const Segment* originalSeg = s; - while ((s = s->next1(SegmentType::ChordRest))) { - if (!segmentsAreAdjacentInRepeatStructure(originalSeg, s)) { - return nullptr; - } - - track_idx_t strack = staffIdx * VOICES; - track_idx_t etrack = strack + VOICES; - // search through all tracks of current staff looking for a lyric in specified verse - for (track_idx_t track = strack; track < etrack; ++track) { - ChordRest* cr = toChordRest(s->element(track)); - if (cr) { - // cr with lyrics found, but does it have a syllable in specified verse? - l = cr->lyrics(verse, p); - if (l) { - break; - } - } - } - if (l) { - break; - } - } - return l; -} - void LyricsLayout::layout(Lyrics* item, LayoutContext& ctx) { if (!item->explicitParent()) { // palette & clone trick diff --git a/src/engraving/rw/read460/read460.cpp b/src/engraving/rw/read460/read460.cpp index a42de02da811a..3b59e5defd63b 100644 --- a/src/engraving/rw/read460/read460.cpp +++ b/src/engraving/rw/read460/read460.cpp @@ -287,6 +287,18 @@ bool Read460::readScoreTag(Score* score, XmlReader& e, ReadContext& ctx) score->connectTies(); + for (Spanner* sp : score->unmanagedSpanners()) { + if (sp->isLyricsLine() && toLyricsLine(sp)->isDash()) { + LyricsLine* line = toLyricsLine(sp); + line->setNextLyrics(searchNextLyrics(line->lyrics()->segment(), + line->staffIdx(), + line->lyrics()->verse(), + line->lyrics()->placement() + )); + line->setTrack2(line->nextLyrics() ? line->nextLyrics()->track() : line->track()); + } + } + score->m_fileDivision = Constants::DIVISION; // Make sure every instrument has an instrumentId set. diff --git a/src/engraving/rw/read460/tread.cpp b/src/engraving/rw/read460/tread.cpp index 495983255d3fb..8a1ce55ba59c2 100644 --- a/src/engraving/rw/read460/tread.cpp +++ b/src/engraving/rw/read460/tread.cpp @@ -238,6 +238,8 @@ void TRead::readItem(EngravingItem* item, XmlReader& xml, ReadContext& ctx) break; case ElementType::LYRICS: read(item_cast(item), xml, ctx); break; + case ElementType::LYRICSLINE: read(item_cast(item), xml, ctx); + break; case ElementType::MARKER: read(item_cast(item), xml, ctx); break; case ElementType::MEASURE_NUMBER: read(item_cast(item), xml, ctx); @@ -3123,6 +3125,15 @@ void TRead::read(Lyrics* l, XmlReader& e, ReadContext& ctx) } } +void TRead::read(LyricsLine* l, XmlReader& e, ReadContext& ctx) +{ + while (e.readNextStartElement()) { + if (!readProperties(static_cast(l), e, ctx)) { + e.unknown(); + } + } +} + void TRead::read(LineSegment* l, XmlReader& e, ReadContext& ctx) { while (e.readNextStartElement()) { @@ -3160,6 +3171,13 @@ bool TRead::readProperties(Lyrics* l, XmlReader& e, ReadContext& ctx) } else if (tag == "ticks_f") { l->setTicks(e.readFraction()); } else if (TRead::readProperty(l, tag, e, ctx, Pid::PLACEMENT)) { + } else if (tag == "LyricsLine") { + LyricsLine* ll = Factory::createLyricsLine(l); + TRead::read(ll, e, ctx); + ll->setParent(l); + ll->setTick(ctx.tick()); + l->setSeparator(ll); + ctx.score()->addUnmanagedSpanner(ll); } else if (!readProperties(toTextBase(l), e, ctx)) { return false; } @@ -3507,7 +3525,7 @@ void TRead::read(PartialLyricsLine* p, XmlReader& xml, ReadContext& ctx) if (tag == "isEndMelisma") { p->setIsEndMelisma(xml.readBool()); } else if (TRead::readProperty(p, tag, xml, ctx, Pid::VERSE)) { - } else if (!readItemProperties(p, xml, ctx)) { + } else if (!readProperties(static_cast(p), xml, ctx)) { xml.unknown(); } } diff --git a/src/engraving/rw/read460/tread.h b/src/engraving/rw/read460/tread.h index f3c3a5c599140..3c0a76ef7560c 100644 --- a/src/engraving/rw/read460/tread.h +++ b/src/engraving/rw/read460/tread.h @@ -103,6 +103,7 @@ class LetRing; class LineSegment; class Location; class Lyrics; +class LyricsLine; class Marker; class MeasureBase; @@ -261,6 +262,7 @@ class TRead static void read(LineSegment* l, XmlReader& xml, ReadContext& ctx); static void read(Location* l, XmlReader& xml, ReadContext& ctx); static void read(Lyrics* l, XmlReader& xml, ReadContext& ctx); + static void read(LyricsLine* l, XmlReader& xml, ReadContext& ctx); static void read(Marker* m, XmlReader& xml, ReadContext& ctx); static void read(MeasureNumber* n, XmlReader& xml, ReadContext& ctx); diff --git a/src/engraving/rw/write/twrite.cpp b/src/engraving/rw/write/twrite.cpp index fbbf19a97c482..733f1d4e393ac 100644 --- a/src/engraving/rw/write/twrite.cpp +++ b/src/engraving/rw/write/twrite.cpp @@ -262,6 +262,8 @@ void TWrite::writeItem(const EngravingItem* item, XmlWriter& xml, WriteContext& break; case ElementType::LYRICS: write(item_cast(item), xml, ctx); break; + case ElementType::LYRICSLINE: write(item_cast(item), xml, ctx); + break; case ElementType::MARKER: write(item_cast(item), xml, ctx); break; case ElementType::MEASURE_NUMBER: write(item_cast(item), xml, ctx); @@ -2187,6 +2189,19 @@ void TWrite::write(const Lyrics* item, XmlWriter& xml, WriteContext& ctx) writeProperty(item, xml, Pid::LYRIC_TICKS); writeProperties(toTextBase(item), xml, ctx, true); + if (item->separator() && !item->separator()->generated()) { + write(item->separator(), xml, ctx); + } + xml.endElement(); +} + +void TWrite::write(const LyricsLine* item, XmlWriter& xml, WriteContext& ctx) +{ + if (!ctx.canWrite(item)) { + return; + } + xml.startElement(item); + writeProperties(static_cast(item), xml, ctx); xml.endElement(); } @@ -2476,7 +2491,7 @@ void TWrite::write(const PartialLyricsLine* item, XmlWriter& xml, WriteContext& xml.startElement(item); writeProperty(item, xml, Pid::VERSE); xml.tag("isEndMelisma", item->isEndMelisma()); - writeItemProperties(item, xml, ctx); + writeProperties(static_cast(item), xml, ctx); xml.endElement(); } diff --git a/src/engraving/rw/write/twrite.h b/src/engraving/rw/write/twrite.h index e3d9fc0db73bd..1819d7fdb7c06 100644 --- a/src/engraving/rw/write/twrite.h +++ b/src/engraving/rw/write/twrite.h @@ -237,6 +237,7 @@ class TWrite static void write(const LetRing* item, XmlWriter& xml, WriteContext& ctx); static void write(const Location* item, XmlWriter& xml, WriteContext& ctx); static void write(const Lyrics* item, XmlWriter& xml, WriteContext& ctx); + static void write(const LyricsLine* item, XmlWriter& xml, WriteContext& ctx); static void write(const Marker* item, XmlWriter& xml, WriteContext& ctx); static void write(const MeasureNumber* item, XmlWriter& xml, WriteContext& ctx); diff --git a/vtest/scores/lyrics-31.mscz b/vtest/scores/lyrics-31.mscz new file mode 100644 index 0000000000000000000000000000000000000000..e506fb607c5994b1deade1c0fda93341cd201946 GIT binary patch literal 25456 zcmY&=r=+1TC~8{2nmXXD)Ao9B1_U*2=(bk%g#^z^xU zx~HnTT16fl0s{mD1_mS|JXT+!vRtWx7X*Yl2owYv1O&w0+}X|2*xkd|!IH_*-MuV8 z-yI;6^7!@{laLS}?@aEsN#U7zH6L;4)c)hI6F-CE?871yg&5ij#Q>x%Alvw>i0Svl z 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 G@P7cOx?4{G literal 0 HcmV?d00001 From 2ff6bf0788160753fb21fd5ae2e4507262119fab Mon Sep 17 00:00:00 2001 From: James Mizen Date: Tue, 16 Dec 2025 16:14:20 +0000 Subject: [PATCH 05/11] Fix setGenerated --- src/engraving/dom/line.cpp | 2 ++ src/engraving/dom/lyrics.h | 1 + src/engraving/dom/lyricsline.cpp | 10 +++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/engraving/dom/line.cpp b/src/engraving/dom/line.cpp index 0142585cb6757..4054d1e8fef79 100644 --- a/src/engraving/dom/line.cpp +++ b/src/engraving/dom/line.cpp @@ -763,6 +763,8 @@ void LineSegment::dragGrip(EditData& ed) } } + undoChangeProperty(Pid::GENERATED, false); + EditTimeTickAnchors::updateAnchors(this); triggerLayout(); diff --git a/src/engraving/dom/lyrics.h b/src/engraving/dom/lyrics.h index c095e7c07f4dc..3f5e002f2df19 100644 --- a/src/engraving/dom/lyrics.h +++ b/src/engraving/dom/lyrics.h @@ -185,6 +185,7 @@ class LyricsLineSegment : public LineSegment PropertyValue getProperty(Pid propertyId) const override; bool setProperty(Pid propertyId, const PropertyValue&) override; PropertyValue propertyDefault(Pid propertyId) const override; + EngravingObject* propertyDelegate(Pid propertyId) const override; bool allowTimeAnchor() const override { return false; } diff --git a/src/engraving/dom/lyricsline.cpp b/src/engraving/dom/lyricsline.cpp index 1c642b1412f5b..e7d0ca6e46f68 100644 --- a/src/engraving/dom/lyricsline.cpp +++ b/src/engraving/dom/lyricsline.cpp @@ -119,7 +119,6 @@ bool LyricsLine::setProperty(Pid propertyId, const engraving::PropertyValue& v) } break; } - setGenerated(false); triggerLayout(); return true; } @@ -218,6 +217,15 @@ PropertyValue LyricsLineSegment::propertyDefault(Pid propertyId) const return LineSegment::propertyDefault(propertyId); } +EngravingObject* LyricsLineSegment::propertyDelegate(Pid propertyId) const +{ + if (propertyId == Pid::GENERATED) { + return lyricsLine(); + } + + return LineSegment::propertyDelegate(propertyId); +} + //========================================================= // PartialLyricsLine //========================================================= From ea1688a2acc3b04ee721756dfe112917efb8efa3 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Tue, 16 Dec 2025 16:16:29 +0000 Subject: [PATCH 06/11] Fix copy paste --- src/engraving/rw/read400/read400.cpp | 12 ++++++++++++ src/engraving/rw/read410/read410.cpp | 12 ++++++++++++ src/engraving/rw/read460/read460.cpp | 13 +++++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/engraving/rw/read400/read400.cpp b/src/engraving/rw/read400/read400.cpp index 708c14d4b5e72..6dccee47550d3 100644 --- a/src/engraving/rw/read400/read400.cpp +++ b/src/engraving/rw/read400/read400.cpp @@ -753,6 +753,18 @@ bool Read400::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract for (Score* s : score->scoreList()) { // for all parts s->connectTies(); + + for (Spanner* sp : score->unmanagedSpanners()) { + if (sp->isLyricsLine() && toLyricsLine(sp)->isDash()) { + LyricsLine* line = toLyricsLine(sp); + line->setNextLyrics(searchNextLyrics(line->lyrics()->segment(), + line->staffIdx(), + line->lyrics()->verse(), + line->lyrics()->placement() + )); + line->setTrack2(line->nextLyrics() ? line->nextLyrics()->track() : line->track()); + } + } } if (pasted) { //select only if we pasted something diff --git a/src/engraving/rw/read410/read410.cpp b/src/engraving/rw/read410/read410.cpp index 1cf95e6812d10..b2986bccd2570 100644 --- a/src/engraving/rw/read410/read410.cpp +++ b/src/engraving/rw/read410/read410.cpp @@ -778,6 +778,18 @@ bool Read410::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract for (Score* s : score->scoreList()) { // for all parts s->connectTies(); + + for (Spanner* sp : score->unmanagedSpanners()) { + if (sp->isLyricsLine() && toLyricsLine(sp)->isDash()) { + LyricsLine* line = toLyricsLine(sp); + line->setNextLyrics(searchNextLyrics(line->lyrics()->segment(), + line->staffIdx(), + line->lyrics()->verse(), + line->lyrics()->placement() + )); + line->setTrack2(line->nextLyrics() ? line->nextLyrics()->track() : line->track()); + } + } } if (pasted) { //select only if we pasted something diff --git a/src/engraving/rw/read460/read460.cpp b/src/engraving/rw/read460/read460.cpp index 3b59e5defd63b..31ff0c11cbe18 100644 --- a/src/engraving/rw/read460/read460.cpp +++ b/src/engraving/rw/read460/read460.cpp @@ -787,6 +787,19 @@ bool Read460::pasteStaff(XmlReader& e, Segment* dst, staff_idx_t dstStaff, Fract for (Score* s : score->scoreList()) { // for all parts s->connectTies(); + + for (Spanner* sp : score->unmanagedSpanners()) { + if (sp->isLyricsLine() && toLyricsLine(sp)->isDash()) { + LOGI() << "dash: " << sp; + LyricsLine* line = toLyricsLine(sp); + line->setNextLyrics(searchNextLyrics(line->lyrics()->segment(), + line->staffIdx(), + line->lyrics()->verse(), + line->lyrics()->placement() + )); + line->setTrack2(line->nextLyrics() ? line->nextLyrics()->track() : line->track()); + } + } } if (pasted) { //select only if we pasted something From 1e5eb98928c539b69c830446bc0eab393d866f43 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 17 Dec 2025 13:35:08 +0000 Subject: [PATCH 07/11] Fix partial lyrics line offset --- src/engraving/dom/lyricsline.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engraving/dom/lyricsline.cpp b/src/engraving/dom/lyricsline.cpp index e7d0ca6e46f68..d81f23ad4ac26 100644 --- a/src/engraving/dom/lyricsline.cpp +++ b/src/engraving/dom/lyricsline.cpp @@ -309,7 +309,6 @@ void PartialLyricsLine::doComputeEndElement() static const ElementStyle partialLyricsLineSegmentElementStyle { { Sid::lyricsPlacement, Pid::PLACEMENT }, - { Sid::lyricsPosBelow, Pid::OFFSET }, { Sid::lyricsMinTopDistance, Pid::MIN_DISTANCE }, }; From 4b5f2acdef9e355c52074a14ced717296b43aa61 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 17 Dec 2025 15:27:28 +0000 Subject: [PATCH 08/11] Delete lyrics lines --- src/engraving/dom/lyrics.cpp | 9 +++++++-- src/engraving/editing/edit.cpp | 12 +++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/engraving/dom/lyrics.cpp b/src/engraving/dom/lyrics.cpp index 5c8d3c9f5c404..823eb1d44b09f 100644 --- a/src/engraving/dom/lyrics.cpp +++ b/src/engraving/dom/lyrics.cpp @@ -91,7 +91,13 @@ TranslatableString Lyrics::subtypeUserName() const void Lyrics::add(EngravingItem* el) { - LOGD("Lyrics::add: unknown element %s", el->typeName()); + if (el->isLyricsLine()) { + LyricsLine* separator = toLyricsLine(el); + m_separator = separator; + score()->addUnmanagedSpanner(separator); + } else { + LOGD("Lyrics::add: unknown element %s", el->typeName()); + } } //--------------------------------------------------------- @@ -107,7 +113,6 @@ void Lyrics::remove(EngravingItem* el) // be sure each finds a clean context LyricsLine* separ = m_separator; m_separator = 0; - separ->resetExplicitParent(); separ->removeUnmanaged(); } } else { diff --git a/src/engraving/editing/edit.cpp b/src/engraving/editing/edit.cpp index aeee19e345652..10e8ab6743d16 100644 --- a/src/engraving/editing/edit.cpp +++ b/src/engraving/editing/edit.cpp @@ -2706,6 +2706,7 @@ void Score::deleteItem(EngravingItem* el) case ElementType::SYSTEM_LOCK_INDICATOR: case ElementType::HAMMER_ON_PULL_OFF_TEXT: case ElementType::PLAY_COUNT_TEXT: + case ElementType::LYRICSLINE_SEGMENT: break; // All other types cannot be removed if generated default: @@ -3134,6 +3135,16 @@ void Score::deleteItem(EngravingItem* el) } break; + case ElementType::LYRICSLINE_SEGMENT: + { + el = toLyricsLineSegment(el)->lyricsLine(); + Lyrics* lyrics = toLyricsLine(el)->lyrics(); + undoRemoveElement(el); + lyrics->undoResetProperty(Pid::LYRIC_TICKS); + lyrics->undoResetProperty(Pid::SYLLABIC); + + break; + } case ElementType::OTTAVA_SEGMENT: case ElementType::HAIRPIN_SEGMENT: case ElementType::TRILL_SEGMENT: @@ -3144,7 +3155,6 @@ void Score::deleteItem(EngravingItem* el) case ElementType::TIE_SEGMENT: case ElementType::LAISSEZ_VIB_SEGMENT: case ElementType::PARTIAL_TIE_SEGMENT: - case ElementType::LYRICSLINE_SEGMENT: case ElementType::PARTIAL_LYRICSLINE_SEGMENT: case ElementType::PEDAL_SEGMENT: case ElementType::GLISSANDO_SEGMENT: From 88f378d6b3a354640aac997e6d2ccde97471f960 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 17 Dec 2025 15:47:35 +0000 Subject: [PATCH 09/11] Adjust lyrics lines by same amount as lyrics --- src/notation/internal/notationinteraction.cpp | 34 +++++++++++++++++-- src/notation/internal/notationinteraction.h | 3 ++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index 94483a0f8d75a..72195da49eed2 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -3401,6 +3401,36 @@ void NotationInteraction::resetAnchorLines() m_anchorLines.clear(); } +double NotationInteraction::getHRaster() const +{ + double hRaster = mu::engraving::MScore::hRaster(); + + if (m_editData.element->isBarLine()) { + // Always adjust by .25sp + hRaster = 4.0; + } else if (m_editData.element->isLyricsLineSegment()) { + // Ignore grid + hRaster = 0.0; + } + + return hRaster; +} + +double NotationInteraction::getVRaster() const +{ + double vRaster = mu::engraving::MScore::vRaster(); + + if (m_editData.element->isBarLine()) { + // Always adjust by .25sp + vRaster = 4.0; + } else if (m_editData.element->isLyricsLineSegment()) { + // Ignore grid + vRaster = 0.0; + } + + return vRaster; +} + double NotationInteraction::currentScaling(Painter* painter) const { qreal guiScaling = configuration()->guiScaling(); @@ -4247,8 +4277,8 @@ void NotationInteraction::nudgeAnchors(MoveDirection d) } startEdit(TranslatableString("undoableAction", "Nudge")); - double vRaster = m_editData.element->isBarLine() ? 4 : mu::engraving::MScore::vRaster(); - double hRaster = m_editData.element->isBarLine() ? 4 : mu::engraving::MScore::hRaster(); + double vRaster = getVRaster(); + double hRaster = getHRaster(); switch (d) { case MoveDirection::Left: diff --git a/src/notation/internal/notationinteraction.h b/src/notation/internal/notationinteraction.h index f1032c409341a..c81514051c11f 100644 --- a/src/notation/internal/notationinteraction.h +++ b/src/notation/internal/notationinteraction.h @@ -423,6 +423,9 @@ class NotationInteraction : public INotationInteraction, public muse::Injectable void setAnchorLines(const std::vector& anchorList); void resetAnchorLines(); + double getHRaster() const; + double getVRaster() const; + double currentScaling(muse::draw::Painter* painter) const; std::vector previewNotes() const; From b41c312acc8c0f287eb267a4225c406d1b4d4ca8 Mon Sep 17 00:00:00 2001 From: James Mizen Date: Wed, 17 Dec 2025 16:01:25 +0000 Subject: [PATCH 10/11] don't reanchor on shift arrow --- src/engraving/dom/lyrics.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/engraving/dom/lyrics.h b/src/engraving/dom/lyrics.h index 3f5e002f2df19..05d49454e7905 100644 --- a/src/engraving/dom/lyrics.h +++ b/src/engraving/dom/lyrics.h @@ -189,6 +189,8 @@ class LyricsLineSegment : public LineSegment bool allowTimeAnchor() const override { return false; } + virtual bool isEditAllowed(EditData&) const override { return false; } + struct LayoutData : public LineSegment::LayoutData { public: const std::vector& dashes() const { return m_dashes; } From 2fa0deb24426093e530da2764c242f6dd5544aff Mon Sep 17 00:00:00 2001 From: James Mizen Date: Fri, 19 Dec 2025 10:02:51 +0000 Subject: [PATCH 11/11] Review fixes --- src/engraving/dom/line.cpp | 2 -- src/engraving/dom/lyrics.cpp | 6 ------ src/engraving/dom/lyrics.h | 2 +- src/engraving/dom/lyricsline.cpp | 2 -- src/engraving/rendering/score/lyricslayout.cpp | 4 ++++ 5 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/engraving/dom/line.cpp b/src/engraving/dom/line.cpp index 4054d1e8fef79..0142585cb6757 100644 --- a/src/engraving/dom/line.cpp +++ b/src/engraving/dom/line.cpp @@ -763,8 +763,6 @@ void LineSegment::dragGrip(EditData& ed) } } - undoChangeProperty(Pid::GENERATED, false); - EditTimeTickAnchors::updateAnchors(this); triggerLayout(); diff --git a/src/engraving/dom/lyrics.cpp b/src/engraving/dom/lyrics.cpp index 823eb1d44b09f..afdd0548fb088 100644 --- a/src/engraving/dom/lyrics.cpp +++ b/src/engraving/dom/lyrics.cpp @@ -596,10 +596,4 @@ void Lyrics::removeInvalidSegments() } } } - -void mu::engraving::PartialLyricsLine::setIsEndMelisma(bool val) -{ - m_isEndMelisma = val; - styleChanged(); -} } diff --git a/src/engraving/dom/lyrics.h b/src/engraving/dom/lyrics.h index 05d49454e7905..c656f209ad0c3 100644 --- a/src/engraving/dom/lyrics.h +++ b/src/engraving/dom/lyrics.h @@ -219,7 +219,7 @@ class PartialLyricsLine final : public LyricsLine Lyrics* lyrics() const override { return nullptr; } - void setIsEndMelisma(bool val); + void setIsEndMelisma(bool val) { m_isEndMelisma = val; } bool isEndMelisma() const override { return m_isEndMelisma; } void setVerse(int val) { m_verse = val; } diff --git a/src/engraving/dom/lyricsline.cpp b/src/engraving/dom/lyricsline.cpp index d81f23ad4ac26..683e48ea7ec36 100644 --- a/src/engraving/dom/lyricsline.cpp +++ b/src/engraving/dom/lyricsline.cpp @@ -128,8 +128,6 @@ PropertyValue LyricsLine::propertyDefault(Pid id) const switch (id) { case Pid::LINE_WIDTH: return styleValue(Pid::LINE_WIDTH, getPropertyStyle(Pid::LINE_WIDTH)); - case Pid::OFFSET: - return lyrics() ? lyrics()->offset() : SLine::propertyDefault(id); default: return SLine::propertyDefault(id); } diff --git a/src/engraving/rendering/score/lyricslayout.cpp b/src/engraving/rendering/score/lyricslayout.cpp index c7dd3b306e309..22d2716c85482 100644 --- a/src/engraving/rendering/score/lyricslayout.cpp +++ b/src/engraving/rendering/score/lyricslayout.cpp @@ -843,6 +843,10 @@ void LyricsLayout::adjustLyricsLineYOffset(LyricsLineSegment* item, const Lyrics Lyrics* nextLyrics = findNextLyrics(endChordRest, item->verse()); if (nextLyrics) { ldata->setPosY(nextLyrics->offset().y()); + } else { + PointF lyricsOffset = item->styleValue(Pid::OFFSET, + item->placeBelow() ? Sid::lyricsPosBelow : Sid::lyricsPosAbove).value(); + ldata->setPosY(lyricsOffset.y()); } return; }