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/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..afdd0548fb088 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/dom/lyrics.h b/src/engraving/dom/lyrics.h index 68a8cc03ae39a..c656f209ad0c3 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,15 @@ 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; + EngravingObject* propertyDelegate(Pid propertyId) const override; + + bool allowTimeAnchor() const override { return false; } + + virtual bool isEditAllowed(EditData&) const override { return false; } struct LayoutData : public LineSegment::LayoutData { public: @@ -199,6 +203,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 diff --git a/src/engraving/dom/lyricsline.cpp b/src/engraving/dom/lyricsline.cpp index d5ea2252bc455..683e48ea7ec36 100644 --- a/src/engraving/dom/lyricsline.cpp +++ b/src/engraving/dom/lyricsline.cpp @@ -40,24 +40,28 @@ 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; + 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); - setLineWidth(style().styleS(Sid::lyricsDashLineThickness)); + 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) @@ -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,42 @@ 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); +} + +EngravingObject* LyricsLineSegment::propertyDelegate(Pid propertyId) const +{ + if (propertyId == Pid::GENERATED) { + return lyricsLine(); + } + + return LineSegment::propertyDelegate(propertyId); +} + //========================================================= // PartialLyricsLine //========================================================= @@ -251,7 +307,6 @@ void PartialLyricsLine::doComputeEndElement() static const ElementStyle partialLyricsLineSegmentElementStyle { { Sid::lyricsPlacement, Pid::PLACEMENT }, - { Sid::lyricsPosBelow, Pid::OFFSET }, { Sid::lyricsMinTopDistance, Pid::MIN_DISTANCE }, }; 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/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/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: diff --git a/src/engraving/rendering/score/lyricslayout.cpp b/src/engraving/rendering/score/lyricslayout.cpp index 45f8942477165..22d2716c85482 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 @@ -197,11 +168,9 @@ void LyricsLayout::layout(Lyrics* item, LayoutContext& ctx) } } -void LyricsLayout::layout(LyricsLine* item, LayoutContext& ctx) +void LyricsLayout::layout(LyricsLine* item) { - 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(), @@ -239,10 +208,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(); @@ -286,9 +251,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())); @@ -336,13 +299,13 @@ 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(); - 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) @@ -367,7 +330,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; } @@ -529,6 +492,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(); @@ -872,23 +836,31 @@ 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()); + } else { + PointF lyricsOffset = item->styleValue(Pid::OFFSET, + item->placeBelow() ? Sid::lyricsPosBelow : Sid::lyricsPosAbove).value(); + ldata->setPosY(lyricsOffset.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); 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 a42de02da811a..31ff0c11cbe18 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. @@ -775,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 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/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 } 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; diff --git a/vtest/scores/lyrics-31.mscz b/vtest/scores/lyrics-31.mscz new file mode 100644 index 0000000000000..e506fb607c599 Binary files /dev/null and b/vtest/scores/lyrics-31.mscz differ