diff --git a/src/engraving/compat/engravingcompat.cpp b/src/engraving/compat/engravingcompat.cpp index 5c907a0082240..99e2b63bb0811 100644 --- a/src/engraving/compat/engravingcompat.cpp +++ b/src/engraving/compat/engravingcompat.cpp @@ -37,6 +37,10 @@ using namespace mu::engraving; namespace mu::engraving::compat { void EngravingCompat::doPreLayoutCompatIfNeeded(MasterScore* score) { + if (score->mscVersion() < 450) { + replaceEmptyCRSegmentsWithTimeTick(score); + } + if (score->mscVersion() >= 440) { resetMarkerLeftFontSize(score); return; @@ -177,6 +181,41 @@ void EngravingCompat::resetMarkerLeftFontSize(MasterScore* masterScore) } } +void EngravingCompat::replaceEmptyCRSegmentsWithTimeTick(MasterScore* masterScore) +{ + auto segmentHasNoChordRest = [](const Segment* segment) { + for (EngravingItem* item : segment->elist()) { + if (item && item->isChordRest()) { + return false; + } + } + return true; + }; + + for (Score* score : masterScore->scoreList()) { + for (MeasureBase* mb = score->first(); mb; mb = mb->next()) { + if (!mb->isMeasure()) { + continue; + } + Measure* measure = toMeasure(mb); + for (Segment* segment = measure->first(SegmentType::ChordRest); segment;) { + Segment* next = segment->next(SegmentType::ChordRest); + if (segmentHasNoChordRest(segment)) { + measure->remove(segment); + Segment* timeTickSegment = measure->getSegmentR(SegmentType::TimeTick, segment->rtick()); + for (EngravingItem* annotation : segment->annotations()) { + segment->remove(annotation); + annotation->setParent(timeTickSegment); + timeTickSegment->add(annotation); + } + delete segment; + } + segment = next; + } + } + } +} + void EngravingCompat::doPostLayoutCompatIfNeeded(MasterScore* score) { if (score->mscVersion() >= 440) { diff --git a/src/engraving/compat/engravingcompat.h b/src/engraving/compat/engravingcompat.h index 13187922dbea1..13967a2b51358 100644 --- a/src/engraving/compat/engravingcompat.h +++ b/src/engraving/compat/engravingcompat.h @@ -39,6 +39,7 @@ class EngravingCompat static void undoStaffTextExcludeFromPart(MasterScore* masterScore); static void migrateDynamicPosOnVocalStaves(MasterScore* masterScore); static void resetMarkerLeftFontSize(MasterScore* masterScore); + static void replaceEmptyCRSegmentsWithTimeTick(MasterScore* masterScore); static bool relayoutUserModifiedCrossStaffBeams(MasterScore* score); }; diff --git a/src/engraving/dom/dynamic.cpp b/src/engraving/dom/dynamic.cpp index ddd0ed924b1fd..2814c47b3f64d 100644 --- a/src/engraving/dom/dynamic.cpp +++ b/src/engraving/dom/dynamic.cpp @@ -139,7 +139,6 @@ Dynamic::Dynamic(const Dynamic& d) _avoidBarLines = d._avoidBarLines; _dynamicsSize = d._dynamicsSize; _centerOnNotehead = d._centerOnNotehead; - m_anchorToEndOfPrevious = d.m_anchorToEndOfPrevious; } //--------------------------------------------------------- @@ -275,22 +274,6 @@ double Dynamic::customTextOffset() const return 0.0; } -bool Dynamic::isEditAllowed(EditData& ed) const -{ - if (ed.editTextualProperties) { - return TextBase::isEditAllowed(ed); - } - - static const std::set ARROW_KEYS { - Key_Left, - Key_Right, - Key_Up, - Key_Down - }; - - return muse::contains(ARROW_KEYS, static_cast(ed.key)); -} - //------------------------------------------------------------------- // doAutoplace // @@ -549,249 +532,6 @@ TranslatableString Dynamic::subtypeUserName() const } } -void Dynamic::startEdit(EditData& ed) -{ - if (ed.editTextualProperties) { - TextBase::startEdit(ed); - } else { - startEditNonTextual(ed); - } -} - -bool Dynamic::edit(EditData& ed) -{ - if (ed.editTextualProperties) { - return TextBase::edit(ed); - } else { - return editNonTextual(ed); - } -} - -bool Dynamic::editNonTextual(EditData& ed) -{ - if (ed.key == Key_Shift) { - if (ed.isKeyRelease) { - score()->hideAnchors(); - } else { - EditTimeTickAnchors::updateAnchors(this, track()); - } - triggerLayout(); - return true; - } - - if (!isEditAllowed(ed)) { - return false; - } - - bool leftRightKey = ed.key == Key_Left || ed.key == Key_Right; - bool altMod = ed.modifiers & AltModifier; - bool shiftMod = ed.modifiers & ShiftModifier; - - bool changeAnchorType = shiftMod && altMod && leftRightKey; - if (changeAnchorType) { - undoChangeProperty(Pid::ANCHOR_TO_END_OF_PREVIOUS, !anchorToEndOfPrevious(), propertyFlags(Pid::ANCHOR_TO_END_OF_PREVIOUS)); - } - bool doesntNeedMoveSeg = changeAnchorType && ((ed.key == Key_Left && anchorToEndOfPrevious()) - || (ed.key == Key_Right && !anchorToEndOfPrevious())); - if (doesntNeedMoveSeg) { - checkMeasureBoundariesAndMoveIfNeed(); - return true; - } - - bool moveSeg = shiftMod && (ed.key == Key_Left || ed.key == Key_Right); - if (moveSeg) { - bool moved = moveSegment(ed); - EditTimeTickAnchors::updateAnchors(this, track()); - checkMeasureBoundariesAndMoveIfNeed(); - return moved; - } - - if (shiftMod) { - return false; - } - - if (!nudge(ed)) { - return false; - } - - triggerLayout(); - return true; -} - -void Dynamic::checkMeasureBoundariesAndMoveIfNeed() -{ - /* Dynamics are always assigned to a ChordRest segment if available at this tick, - * EXCEPT if we are at a measure boundary. In this case, if anchorToEndOfPrevious() - * we must assign to a TimeTick segment at the end of previous measure, otherwise to a - * ChordRest segment at the start of the next measure. */ - - Segment* curSeg = segment(); - Fraction curTick = curSeg->tick(); - Measure* curMeasure = curSeg->measure(); - Measure* prevMeasure = curMeasure->prevMeasure(); - bool needMoveToNext = curTick == curMeasure->endTick() && !anchorToEndOfPrevious(); - bool needMoveToPrevious = curSeg->rtick().isZero() && anchorToEndOfPrevious() && prevMeasure; - - if (!needMoveToPrevious && !needMoveToNext) { - return; - } - - Segment* newSeg = nullptr; - if (needMoveToPrevious) { - newSeg = prevMeasure->findSegment(SegmentType::TimeTick, curSeg->tick()); - if (!newSeg) { - TimeTickAnchor* anchor = EditTimeTickAnchors::createTimeTickAnchor(prevMeasure, curTick - prevMeasure->tick(), staffIdx()); - EditTimeTickAnchors::updateLayout(prevMeasure); - newSeg = anchor->segment(); - } - } else { - newSeg = curSeg->next1(SegmentType::ChordRest); - } - - if (newSeg && newSeg->tick() == curTick) { - score()->undoChangeParent(this, newSeg, staffIdx()); - if (snappedExpression()) { - score()->undoChangeParent(snappedExpression(), newSeg, staffIdx()); - } - } -} - -bool Dynamic::moveSegment(const EditData& ed) -{ - bool forward = ed.key == Key_Right; - if (!(ed.modifiers & AltModifier)) { - if (anchorToEndOfPrevious()) { - undoResetProperty(Pid::ANCHOR_TO_END_OF_PREVIOUS); - if (forward) { - return true; - } - } - } - - Segment* curSeg = segment(); - IF_ASSERT_FAILED(curSeg) { - return false; - } - - Segment* newSeg = forward ? curSeg->next1ChordRestOrTimeTick() : curSeg->prev1ChordRestOrTimeTick(); - if (!newSeg) { - return false; - } - - undoMoveSegment(newSeg, newSeg->tick() - curSeg->tick()); - - return true; -} - -void Dynamic::undoMoveSegment(Segment* newSeg, Fraction tickDiff) -{ - score()->undoChangeParent(this, newSeg, staffIdx()); - moveSnappedItems(newSeg, tickDiff); -} - -void Dynamic::moveSnappedItems(Segment* newSeg, Fraction tickDiff) const -{ - if (EngravingItem* itemSnappedBefore = ldata()->itemSnappedBefore()) { - if (itemSnappedBefore->isHairpinSegment()) { - Hairpin* hairpinBefore = toHairpinSegment(itemSnappedBefore)->hairpin(); - hairpinBefore->undoMoveEnd(tickDiff); - } - } - - if (EngravingItem* itemSnappedAfter = ldata()->itemSnappedAfter()) { - Hairpin* hairpinAfter = itemSnappedAfter->isHairpinSegment() ? toHairpinSegment(itemSnappedAfter)->hairpin() : nullptr; - if (itemSnappedAfter->isExpression()) { - Expression* expressionAfter = toExpression(itemSnappedAfter); - Segment* curExpressionSegment = expressionAfter->segment(); - if (curExpressionSegment != newSeg) { - score()->undoChangeParent(expressionAfter, newSeg, expressionAfter->staffIdx()); - } - EngravingItem* possibleHairpinAfterExpr = expressionAfter->ldata()->itemSnappedAfter(); - if (!hairpinAfter && possibleHairpinAfterExpr && possibleHairpinAfterExpr->isHairpinSegment()) { - hairpinAfter = toHairpinSegment(possibleHairpinAfterExpr)->hairpin(); - } - } - if (hairpinAfter) { - hairpinAfter->undoMoveStart(tickDiff); - } - } -} - -bool Dynamic::nudge(const EditData& ed) -{ - bool ctrlMod = ed.modifiers & ControlModifier; - double step = spatium() * (ctrlMod ? MScore::nudgeStep10 : MScore::nudgeStep); - PointF addOffset = PointF(); - switch (ed.key) { - case Key_Up: - addOffset = PointF(0.0, -step); - break; - case Key_Down: - addOffset = PointF(0.0, step); - break; - case Key_Left: - addOffset = PointF(-step, 0.0); - break; - case Key_Right: - addOffset = PointF(step, 0.0); - break; - default: - return false; - } - undoChangeProperty(Pid::OFFSET, offset() + addOffset, PropertyFlags::UNSTYLED); - return true; -} - -void Dynamic::editDrag(EditData& ed) -{ - ElementEditDataPtr eed = ed.getData(this); - if (!eed) { - return; - } - - EditTimeTickAnchors::updateAnchors(this, track()); - - KeyboardModifiers km = ed.modifiers; - if (km != (ShiftModifier | ControlModifier)) { - staff_idx_t si = staffIdx(); - Segment* seg = nullptr; // don't prefer any segment while dragging, just snap to the closest - static constexpr double spacingFactor = 0.5; - score()->dragPosition(canvasPos(), &si, &seg, spacingFactor, allowTimeAnchor()); - if ((seg && seg != segment()) || staffIdx() != si) { - const PointF oldOffset = offset(); - PointF pos1(canvasPos()); - score()->undoChangeParent(this, seg, staffIdx()); - Expression* snappedExpr = snappedExpression(); - if (snappedExpr) { - score()->undoChangeParent(snappedExpr, seg, staffIdx()); - } - setOffset(PointF()); - - renderer()->layoutItem(this); - - PointF pos2(canvasPos()); - const PointF newOffset = pos1 - pos2; - setOffset(newOffset); - setOffsetChanged(true); - eed->initOffset += newOffset - oldOffset; - } - } - - EngravingItem::editDrag(ed); -} - -void Dynamic::endEdit(EditData& ed) -{ - if (ed.editTextualProperties) { - TextBase::endEdit(ed); - if (!xmlText().contains(String::fromUtf8(DYN_LIST[int(m_dynamicType)].text))) { - m_dynamicType = DynamicType::OTHER; - } - } else { - endEditNonTextual(ed); - } -} - //--------------------------------------------------------- // reset //--------------------------------------------------------- diff --git a/src/engraving/dom/dynamic.h b/src/engraving/dom/dynamic.h index 29a8872ba0d66..27e24976d9b56 100644 --- a/src/engraving/dom/dynamic.h +++ b/src/engraving/dom/dynamic.h @@ -70,17 +70,7 @@ class Dynamic final : public TextBase double customTextOffset() const; - bool isEditable() const override { return true; } - bool isEditAllowed(EditData&) const override; - void startEdit(EditData&) override; - bool edit(EditData&) override; - bool editNonTextual(EditData&) override; - void editDrag(EditData&) override; - void endEdit(EditData&) override; void reset() override; - bool needStartEditingAfterSelecting() const override { return true; } - - bool allowTimeAnchor() const override { return true; } void setVelocity(int v) { m_velocity = v; } int velocity() const; @@ -113,8 +103,6 @@ class Dynamic final : public TextBase Expression* snappedExpression() const; HairpinSegment* findSnapBeforeHairpinAcrossSystemBreak() const; - void undoMoveSegment(Segment* newSeg, Fraction tickDiff); - bool playDynamic() const { return m_playDynamic; } void setPlayDynamic(bool v) { m_playDynamic = v; } @@ -124,10 +112,6 @@ class Dynamic final : public TextBase static int dynamicVelocity(DynamicType t); static const std::vector& dynamicList() { return DYN_LIST; } - bool anchorToEndOfPrevious() const { return m_anchorToEndOfPrevious; } - void setAnchorToEndOfPrevious(bool v) { m_anchorToEndOfPrevious = v; } - void checkMeasureBoundariesAndMoveIfNeed(); - bool hasVoiceAssignmentProperties() const override { return true; } private: @@ -136,12 +120,7 @@ class Dynamic final : public TextBase M_PROPERTY(double, dynamicsSize, setDynamicsSize) M_PROPERTY(bool, centerOnNotehead, setCenterOnNotehead) - bool moveSegment(const EditData& ed); - void moveSnappedItems(Segment* newSeg, Fraction tickDiff) const; - bool nudge(const EditData& ed); - std::pair parseDynamicText(const String&) const; - DynamicType m_dynamicType = DynamicType::OTHER; bool m_playDynamic = true; @@ -153,8 +132,6 @@ class Dynamic final : public TextBase DynamicSpeed m_velChangeSpeed = DynamicSpeed::NORMAL; static const std::vector DYN_LIST; - - bool m_anchorToEndOfPrevious = false; }; } // namespace mu::engraving diff --git a/src/engraving/dom/dynamichairpingroup.cpp b/src/engraving/dom/dynamichairpingroup.cpp index 09e4a0d8e5ec2..6d927841c1637 100644 --- a/src/engraving/dom/dynamichairpingroup.cpp +++ b/src/engraving/dom/dynamichairpingroup.cpp @@ -234,19 +234,8 @@ void DynamicExpressionDragGroup::startDrag(EditData& ed) RectF DynamicExpressionDragGroup::drag(EditData& ed) { - RectF r = static_cast(m_dynamic)->drag(ed); - - // Dynamic may snap to a different segment upon dragging, - // in which case move the expression with it - Segment* newSegment = m_dynamic->segment(); - Segment* oldSegment = toSegment(m_expression->explicitParent()); - staff_idx_t newStaff = m_dynamic->staffIdx(); - staff_idx_t oldStaff = m_expression->staffIdx(); - - if (newSegment != oldSegment || newStaff != oldStaff) { - Score* score = newSegment->score(); - score->undoChangeParent(m_expression, newSegment, newStaff); - } + RectF r = m_dynamic->drag(ed); + r.unite(m_expression->drag(ed)); m_dynamic->triggerLayout(); m_expression->triggerLayout(); diff --git a/src/engraving/dom/figuredbass.cpp b/src/engraving/dom/figuredbass.cpp index b8804b2448654..adef3dd786781 100644 --- a/src/engraving/dom/figuredbass.cpp +++ b/src/engraving/dom/figuredbass.cpp @@ -702,18 +702,14 @@ Sid FiguredBass::getPropertyStyle(Pid id) const return EngravingItem::getPropertyStyle(id); } -//--------------------------------------------------------- -// startEdit / edit / endEdit -//--------------------------------------------------------- - -void FiguredBass::startEdit(EditData& ed) +void FiguredBass::startEditTextual(EditData& ed) { clearItems(); renderer()->layoutText1(this); // re-layout without F.B.-specific formatting. - TextBase::startEdit(ed); + TextBase::startEditTextual(ed); } -bool FiguredBass::isEditAllowed(EditData& ed) const +bool FiguredBass::isTextualEditAllowed(EditData& ed) const { if (isTextNavigationKey(ed.key, ed.modifiers)) { return false; @@ -723,12 +719,12 @@ bool FiguredBass::isEditAllowed(EditData& ed) const return false; } - return TextBase::isEditAllowed(ed); + return TextBase::isTextualEditAllowed(ed); } -void FiguredBass::endEdit(EditData& ed) +void FiguredBass::endEditTextual(EditData& ed) { - TextBase::endEdit(ed); + TextBase::endEditTextual(ed); regenerateText(); } @@ -776,6 +772,47 @@ void FiguredBass::regenerateText() score()->endCmd(); } +void FiguredBass::undoMoveSegment(Segment* newSeg, Fraction tickDiff) +{ + Segment* oldSeg = segment(); + + TextBase::undoMoveSegment(newSeg, tickDiff); + + track_idx_t startTrack = staff2track(staffIdx()); + track_idx_t endTrack = startTrack + VOICES; + + // Shorten this if needed + if (newSeg->tick() > oldSeg->tick()) { + FiguredBass* nextFB = nullptr; + Fraction endTick = newSeg->tick() + m_ticks; + for (Segment* seg = newSeg->next1(Segment::CHORD_REST_OR_TIME_TICK_TYPE); seg && seg->tick() <= endTick; + seg = seg->next1(Segment::CHORD_REST_OR_TIME_TICK_TYPE)) { + nextFB = toFiguredBass(seg->findAnnotation(ElementType::FIGURED_BASS, startTrack, endTrack)); + if (nextFB) { + break; + } + } + if (nextFB) { + setTicks(nextFB->tick() - newSeg->tick()); + } + } + + // Shorten previous if needed + if (newSeg->tick() < oldSeg->tick()) { + FiguredBass* prevFB = nullptr; + for (Segment* seg = newSeg->prev1(Segment::CHORD_REST_OR_TIME_TICK_TYPE); seg && seg->measure()->isAfterOrEqual(newSeg->measure()); + seg = seg->prev1(Segment::CHORD_REST_OR_TIME_TICK_TYPE)) { + prevFB = (FiguredBass*)(seg->findAnnotation(ElementType::FIGURED_BASS, startTrack, endTrack)); + if (prevFB) { + break; + } + } + if (prevFB) { + prevFB->setTicks(std::min(prevFB->ticks(), newSeg->tick() - prevFB->tick())); + } + } +} + //--------------------------------------------------------- // setSelected /setVisible // @@ -1011,7 +1048,8 @@ FiguredBass* FiguredBass::addFiguredBassToSegment(Segment* seg, track_idx_t trac // locate previous FB for same staff Segment* prevSegm; FiguredBass* prevFB = 0; - for (prevSegm = seg->prev1(SegmentType::ChordRest); prevSegm; prevSegm = prevSegm->prev1(SegmentType::ChordRest)) { + for (prevSegm = seg->prev1(Segment::CHORD_REST_OR_TIME_TICK_TYPE); prevSegm; + prevSegm = prevSegm->prev1(Segment::CHORD_REST_OR_TIME_TICK_TYPE)) { for (EngravingItem* e : prevSegm->annotations()) { if (e->type() == ElementType::FIGURED_BASS && (e->track()) == track) { prevFB = toFiguredBass(e); // previous FB found diff --git a/src/engraving/dom/figuredbass.h b/src/engraving/dom/figuredbass.h index 49e41e57f95f4..e107a12861779 100644 --- a/src/engraving/dom/figuredbass.h +++ b/src/engraving/dom/figuredbass.h @@ -273,11 +273,13 @@ class FiguredBass final : public TextBase void setSelected(bool f) override; void setVisible(bool f) override; - void startEdit(EditData&) override; - bool isEditAllowed(EditData&) const override; - void endEdit(EditData&) override; + void startEditTextual(EditData& ed) override; + bool isTextualEditAllowed(EditData&) const override; + void endEditTextual(EditData&) override; void regenerateText(); + void undoMoveSegment(Segment* newSeg, Fraction tickDiff) override; + bool onNote() const { return m_onNote; } void setOnNote(bool val) { m_onNote = val; } Segment* segment() const { return (Segment*)(explicitParent()); } diff --git a/src/engraving/dom/harmony.cpp b/src/engraving/dom/harmony.cpp index 2ad099d392835..f1ce0f5e42110 100644 --- a/src/engraving/dom/harmony.cpp +++ b/src/engraving/dom/harmony.cpp @@ -665,7 +665,7 @@ const ChordDescription* Harmony::parseHarmony(const String& ss, int* root, int* // startEdit //--------------------------------------------------------- -void Harmony::startEdit(EditData& ed) +void Harmony::startEditTextual(EditData& ed) { if (!m_textList.empty()) { // convert chord symbol to plain text @@ -681,10 +681,10 @@ void Harmony::startEdit(EditData& ed) renderer()->layoutText1(this, true); triggerLayout(); - TextBase::startEdit(ed); + TextBase::startEditTextual(ed); } -bool Harmony::isEditAllowed(EditData& ed) const +bool Harmony::isTextualEditAllowed(EditData& ed) const { if (isTextNavigationKey(ed.key, ed.modifiers)) { return false; @@ -703,20 +703,20 @@ bool Harmony::isEditAllowed(EditData& ed) const return true; } - return TextBase::isEditAllowed(ed); + return TextBase::isTextualEditAllowed(ed); } //--------------------------------------------------------- // edit //--------------------------------------------------------- -bool Harmony::edit(EditData& ed) +bool Harmony::editTextual(EditData& ed) { if (!isEditAllowed(ed)) { return false; } - bool rv = TextBase::edit(ed); + bool rv = TextBase::editTextual(ed); // layout as text, without position reset renderer()->layoutText1(this, true); @@ -741,7 +741,7 @@ bool Harmony::edit(EditData& ed) // endEdit //--------------------------------------------------------- -void Harmony::endEdit(EditData& ed) +void Harmony::endEditTextual(EditData& ed) { // get plain text String s = plainText(); @@ -779,7 +779,7 @@ void Harmony::endEdit(EditData& ed) // disable spell check m_isMisspelled = false; - TextBase::endEdit(ed); + TextBase::endEditTextual(ed); if (links()) { for (EngravingObject* e : *links()) { diff --git a/src/engraving/dom/harmony.h b/src/engraving/dom/harmony.h index 1c5db5bfaafaf..90f108e7c9012 100644 --- a/src/engraving/dom/harmony.h +++ b/src/engraving/dom/harmony.h @@ -128,10 +128,10 @@ class Harmony final : public TextBase void textChanged(); bool isEditable() const override { return true; } - void startEdit(EditData&) override; - bool isEditAllowed(EditData&) const override; - bool edit(EditData&) override; - void endEdit(EditData&) override; + void startEditTextual(EditData&) override; + bool isTextualEditAllowed(EditData&) const override; + bool editTextual(EditData&) override; + void endEditTextual(EditData&) override; bool isRealizable() const; diff --git a/src/engraving/dom/line.cpp b/src/engraving/dom/line.cpp index bade8694468c7..6b13f0ffd00e7 100644 --- a/src/engraving/dom/line.cpp +++ b/src/engraving/dom/line.cpp @@ -890,9 +890,9 @@ void LineSegment::undoMoveStartEndAndSnappedItems(bool moveStart, bool moveEnd, if (moveStart) { Fraction tickDiff = s1->tick() - thisLine->tick(); if (EngravingItem* itemSnappedBefore = ldata()->itemSnappedBefore()) { - if (itemSnappedBefore->isDynamic()) { - // Let the dynamic manage the move - toDynamic(itemSnappedBefore)->undoMoveSegment(s1, tickDiff); + if (itemSnappedBefore->isTextBase()) { + // Let the TextBase manage the move + toTextBase(itemSnappedBefore)->undoMoveSegment(s1, tickDiff); } else if (itemSnappedBefore->isLineSegment()) { toLineSegment(itemSnappedBefore)->line()->undoMoveEnd(tickDiff); thisLine->undoMoveStart(tickDiff); @@ -904,9 +904,9 @@ void LineSegment::undoMoveStartEndAndSnappedItems(bool moveStart, bool moveEnd, if (moveEnd) { Fraction tickDiff = s2->tick() - thisLine->tick2(); if (EngravingItem* itemSnappedAfter = thisLine->backSegment()->ldata()->itemSnappedAfter()) { - if (itemSnappedAfter->isDynamic()) { - // Let the dynamic manage the move - toDynamic(itemSnappedAfter)->undoMoveSegment(s2, tickDiff); + if (itemSnappedAfter->isTextBase()) { + // Let the TextBase manage the move + toTextBase(itemSnappedAfter)->undoMoveSegment(s2, tickDiff); } else if (itemSnappedAfter->isLineSegment()) { toLineSegment(itemSnappedAfter)->line()->undoMoveStart(tickDiff); thisLine->undoMoveEnd(tickDiff); diff --git a/src/engraving/dom/measurebase.h b/src/engraving/dom/measurebase.h index a1f7822d37269..698e9bd0ee303 100644 --- a/src/engraving/dom/measurebase.h +++ b/src/engraving/dom/measurebase.h @@ -184,6 +184,7 @@ class MeasureBase : public EngravingItem bool isBefore(const MeasureBase* other) const; bool isBeforeOrEqual(const MeasureBase* other) const { return other == this || isBefore(other); } bool isAfter(const MeasureBase* other) const { return !isBeforeOrEqual(other); } + bool isAfterOrEqual(const MeasureBase* other) const { return !isBefore(other); } const SystemLock* systemLock() const; bool isStartOfSystemLock() const; diff --git a/src/engraving/dom/score.cpp b/src/engraving/dom/score.cpp index cfe676952b54b..c91bbbc266788 100644 --- a/src/engraving/dom/score.cpp +++ b/src/engraving/dom/score.cpp @@ -1564,7 +1564,6 @@ void Score::addElement(EngravingItem* element) break; case ElementType::DYNAMIC: - toDynamic(element)->checkMeasureBoundariesAndMoveIfNeed(); setPlaylistDirty(); break; @@ -1617,6 +1616,11 @@ void Score::addElement(EngravingItem* element) default: break; } + + if (element->isTextBase() && toTextBase(element)->hasParentSegment()) { + toTextBase(element)->checkMeasureBoundariesAndMoveIfNeed(); + } + element->triggerLayout(); } diff --git a/src/engraving/dom/textbase.cpp b/src/engraving/dom/textbase.cpp index da8761735a1fd..ff373f983ab5a 100644 --- a/src/engraving/dom/textbase.cpp +++ b/src/engraving/dom/textbase.cpp @@ -42,6 +42,7 @@ #include "accessibility/accessibleitem.h" #endif +#include "anchors.h" #include "barline.h" #include "box.h" #include "dynamic.h" @@ -51,6 +52,7 @@ #include "page.h" #include "score.h" #include "textedit.h" +#include "textline.h" #include "undo.h" #include "log.h" @@ -1723,6 +1725,7 @@ TextBase::TextBase(const TextBase& st) m_voiceAssignment = st.m_voiceAssignment; m_direction = st.m_direction; m_centerBetweenStaves = st.m_centerBetweenStaves; + m_anchorToEndOfPrevious = st.m_anchorToEndOfPrevious; size_t n = m_elementStyle->size() + TEXT_STYLE_SIZE; delete[] m_propertyFlagsList; @@ -3212,6 +3215,273 @@ void TextBase::initTextStyleType(TextStyleType tid) } } +void TextBase::startEdit(EditData& ed) +{ + if (ed.editTextualProperties) { + startEditTextual(ed); + } else { + startEditNonTextual(ed); + } +} + +bool TextBase::isEditAllowed(EditData& ed) const +{ + if (ed.editTextualProperties) { + return isTextualEditAllowed(ed); + } else { + return isNonTextualEditAllowed(ed); + } +} + +bool TextBase::edit(EditData& ed) +{ + if (ed.editTextualProperties) { + return editTextual(ed); + } else { + return editNonTextual(ed); + } +} + +void TextBase::endEdit(EditData& ed) +{ + if (ed.editTextualProperties) { + endEditTextual(ed); + } else { + endEditNonTextual(ed); + } +} + +void TextBase::editDrag(EditData& ed) +{ + Segment* segment = explicitParent() ? toSegment(parent()) : nullptr; + if (!segment) { + return EngravingItem::editDrag(ed); + } + + ElementEditDataPtr eed = ed.getData(this); + if (!eed) { + return; + } + + EditTimeTickAnchors::updateAnchors(this, track()); + + KeyboardModifiers km = ed.modifiers; + if (km & (ShiftModifier | ControlModifier)) { + return; + } + + staff_idx_t si = staffIdx(); + Segment* newSeg = nullptr; // don't prefer any segment while dragging, just snap to the closest + static constexpr double spacingFactor = 0.5; + score()->dragPosition(canvasPos(), &si, &newSeg, spacingFactor, allowTimeAnchor()); + if (newSeg && (newSeg != segment || staffIdx() != si)) { + undoMoveSegment(newSeg, newSeg->tick() - segment->tick()); + double deltaX = newSeg->pageX() - segment->pageX(); + PointF offsetShift = PointF(deltaX, 0.0); + shiftInitOffset(ed, offsetShift); + } +} + +void TextBase::shiftInitOffset(EditData& ed, const PointF& offsetShift) +{ + ElementEditDataPtr eedThis = ed.getData(this); + eedThis->initOffset -= offsetShift; + + if (EngravingItem* itemBefore = ldata()->itemSnappedBefore()) { + if (itemBefore->isTextBase()) { + ElementEditDataPtr eedBefore = ed.getData(itemBefore); + if (eedBefore) { + eedBefore->initOffset -= offsetShift; + } + } + } + + if (EngravingItem* itemAfter = ldata()->itemSnappedAfter()) { + if (itemAfter->isTextBase()) { + ElementEditDataPtr eedAfter = ed.getData(itemAfter); + if (eedAfter) { + eedAfter->initOffset -= offsetShift; + } + } + } +} + +void TextBase::startEditNonTextual(EditData& ed) +{ + EngravingItem::startEdit(ed); +} + +bool TextBase::editNonTextual(EditData& ed) +{ + if (!hasParentSegment()) { + return EngravingItem::edit(ed); + } + + if (ed.key == Key_Shift) { + if (ed.isKeyRelease) { + score()->hideAnchors(); + } else { + EditTimeTickAnchors::updateAnchors(this, track()); + } + triggerLayout(); + return true; + } + + if (!isEditAllowed(ed)) { + return false; + } + + bool leftRightKey = ed.key == Key_Left || ed.key == Key_Right; + bool altMod = ed.modifiers & AltModifier; + bool shiftMod = ed.modifiers & ShiftModifier; + + bool changeAnchorType = shiftMod && altMod && leftRightKey; + if (changeAnchorType) { + undoChangeProperty(Pid::ANCHOR_TO_END_OF_PREVIOUS, !anchorToEndOfPrevious(), propertyFlags(Pid::ANCHOR_TO_END_OF_PREVIOUS)); + } + bool doesntNeedMoveSeg = changeAnchorType && ((ed.key == Key_Left && anchorToEndOfPrevious()) + || (ed.key == Key_Right && !anchorToEndOfPrevious())); + if (doesntNeedMoveSeg) { + checkMeasureBoundariesAndMoveIfNeed(); + return true; + } + + bool moveSeg = shiftMod && (ed.key == Key_Left || ed.key == Key_Right); + if (moveSeg) { + bool moved = moveSegment(ed); + EditTimeTickAnchors::updateAnchors(this, track()); + checkMeasureBoundariesAndMoveIfNeed(); + return moved; + } + + if (shiftMod) { + return false; + } + + if (!nudge(ed)) { + return false; + } + + triggerLayout(); + return true; +} + +void TextBase::endEditNonTextual(EditData& ed) +{ + EngravingItem::endEdit(ed); +} + +bool TextBase::isNonTextualEditAllowed(EditData& ed) const +{ + static const std::set ARROW_KEYS { + Key_Left, + Key_Right, + Key_Up, + Key_Down + }; + + return muse::contains(ARROW_KEYS, static_cast(ed.key)); +} + +void TextBase::checkMeasureBoundariesAndMoveIfNeed() +{ + assert(hasParentSegment()); + + /* TextBase elements are always assigned to a ChordRest segment if available at this tick, + * EXCEPT if we are at a measure boundary. In this case, if anchorToEndOfPrevious() + * we must assign to a TimeTick segment at the end of previous measure, otherwise to a + * ChordRest segment at the start of the next measure. */ + + Segment* curSeg = toSegment(parent()); + Fraction curTick = curSeg->tick(); + Measure* curMeasure = curSeg->measure(); + Measure* prevMeasure = curMeasure->prevMeasure(); + bool needMoveToNext = curTick == curMeasure->endTick() && !anchorToEndOfPrevious(); + bool needMoveToPrevious = curSeg->rtick().isZero() && anchorToEndOfPrevious() && prevMeasure; + + if (!needMoveToPrevious && !needMoveToNext) { + return; + } + + Segment* newSeg = nullptr; + if (needMoveToPrevious) { + newSeg = prevMeasure->findSegment(SegmentType::TimeTick, curSeg->tick()); + if (!newSeg) { + TimeTickAnchor* anchor = EditTimeTickAnchors::createTimeTickAnchor(prevMeasure, curTick - prevMeasure->tick(), staffIdx()); + EditTimeTickAnchors::updateLayout(prevMeasure); + newSeg = anchor->segment(); + } + } else { + newSeg = curSeg->next1(SegmentType::ChordRest); + } + + if (newSeg && newSeg->tick() == curTick) { + undoMoveSegment(newSeg, Fraction(0, 1)); + } +} + +bool TextBase::moveSegment(const EditData& ed) +{ + assert(hasParentSegment()); + + bool forward = ed.key == Key_Right; + if (!(ed.modifiers & AltModifier)) { + if (anchorToEndOfPrevious()) { + undoResetProperty(Pid::ANCHOR_TO_END_OF_PREVIOUS); + if (forward) { + return true; + } + } + } + + Segment* curSeg = toSegment(parent()); + IF_ASSERT_FAILED(curSeg) { + return false; + } + + Segment* newSeg = forward ? curSeg->next1ChordRestOrTimeTick() : curSeg->prev1ChordRestOrTimeTick(); + if (!newSeg) { + return false; + } + + undoMoveSegment(newSeg, newSeg->tick() - curSeg->tick()); + + return true; +} + +void TextBase::undoMoveSegment(Segment* newSeg, Fraction tickDiff) +{ + score()->undoChangeParent(this, newSeg, staffIdx()); + moveSnappedItems(newSeg, tickDiff); +} + +void TextBase::moveSnappedItems(Segment* newSeg, Fraction tickDiff) const +{ + if (EngravingItem* itemAfter = ldata()->itemSnappedAfter()) { + if (itemAfter->isTextBase() && itemAfter->parent() != newSeg) { + score()->undoChangeParent(itemAfter, newSeg, itemAfter->staffIdx()); + toTextBase(itemAfter)->moveSnappedItems(newSeg, tickDiff); + } else if (itemAfter->isTextLineBaseSegment()) { + TextLineBase* textLine = ((TextLineBaseSegment*)itemAfter)->textLineBase(); + if (textLine->tick() != newSeg->tick()) { + textLine->undoMoveStart(tickDiff); + } + } + } + + if (EngravingItem* itemBefore = ldata()->itemSnappedBefore()) { + if (itemBefore->isTextBase() && itemBefore->parent() != newSeg) { + score()->undoChangeParent(itemBefore, newSeg, itemBefore->staffIdx()); + toTextBase(itemBefore)->moveSnappedItems(newSeg, tickDiff); + } else if (itemBefore->isTextLineSegment()) { + TextLineBase* textLine = ((TextLineBaseSegment*)itemBefore)->textLineBase(); + if (textLine->tick2() != newSeg->tick()) { + textLine->undoMoveEnd(tickDiff); + } + } + } +} + //--------------------------------------------------------- // editCut //--------------------------------------------------------- @@ -3246,6 +3516,31 @@ void TextBase::editCopy(EditData& ed) ted->selectedPlainText = cursor->selectedText(false); } +bool TextBase::nudge(const EditData& ed) +{ + bool ctrlMod = ed.modifiers & ControlModifier; + double step = spatium() * (ctrlMod ? MScore::nudgeStep10 : MScore::nudgeStep); + PointF addOffset = PointF(); + switch (ed.key) { + case Key_Up: + addOffset = PointF(0.0, -step); + break; + case Key_Down: + addOffset = PointF(0.0, step); + break; + case Key_Left: + addOffset = PointF(-step, 0.0); + break; + case Key_Right: + addOffset = PointF(step, 0.0); + break; + default: + return false; + } + undoChangeProperty(Pid::OFFSET, offset() + addOffset, PropertyFlags::UNSTYLED); + return true; +} + //--------------------------------------------------------- // cursor //--------------------------------------------------------- @@ -3320,7 +3615,7 @@ void TextBase::drawEditMode(Painter* p, EditData& ed, double currentViewScaling) } p->translate(-pos); - p->setPen(Pen(configuration()->formattingColor(), 2.0 / currentViewScaling)); // 2 pixel pen size + p->setPen(Pen(configuration()->frameColor(), 2.0 / currentViewScaling)); // 2 pixel pen size p->setBrush(BrushStyle::NoBrush); double m = spatium(); diff --git a/src/engraving/dom/textbase.h b/src/engraving/dom/textbase.h index 4137f5e3b835b..25632b665c60a 100644 --- a/src/engraving/dom/textbase.h +++ b/src/engraving/dom/textbase.h @@ -337,17 +337,23 @@ class TextBase : public EngravingItem void setFamily(const String& val); void setSize(const double& val); + bool anchorToEndOfPrevious() const { return m_anchorToEndOfPrevious; } + void setAnchorToEndOfPrevious(bool v) { m_anchorToEndOfPrevious = v; } + + bool hasParentSegment() const { return explicitParent() && parent()->isSegment(); } + virtual bool needStartEditingAfterSelecting() const override { return hasParentSegment(); } + virtual bool allowTimeAnchor() const override { return hasParentSegment(); } virtual void startEdit(EditData&) override; virtual bool isEditAllowed(EditData&) const override; virtual bool edit(EditData&) override; virtual void editCut(EditData&) override; virtual void editCopy(EditData&) override; virtual void endEdit(EditData&) override; + virtual void editDrag(EditData&) override; void movePosition(EditData&, TextCursor::MoveOperation); - virtual void startEditNonTextual(EditData&); - virtual bool editNonTextual(EditData&); - virtual void endEditNonTextual(EditData&); + virtual void undoMoveSegment(Segment* newSeg, Fraction tickDiff); + void checkMeasureBoundariesAndMoveIfNeed(); bool deleteSelectedText(EditData&); @@ -495,6 +501,19 @@ class TextBase : public EngravingItem TextBase(const ElementType& type, EngravingItem* parent, ElementFlags); TextBase(const TextBase&); + virtual void startEditTextual(EditData&); + virtual void startEditNonTextual(EditData&); + virtual bool editTextual(EditData&); + virtual bool editNonTextual(EditData&); + virtual void endEditNonTextual(EditData&); + virtual void endEditTextual(EditData&); + virtual bool isNonTextualEditAllowed(EditData&) const; + virtual bool isTextualEditAllowed(EditData&) const; + bool nudge(const EditData& ed); + + bool moveSegment(const EditData&); + void moveSnappedItems(Segment* newSeg, Fraction tickDiff) const; + void insertSym(EditData& ed, SymId id); void prepareFormat(const String& token, TextCursor& cursor); bool prepareFormat(const String& token, CharFormat& format); @@ -524,6 +543,8 @@ class TextBase : public EngravingItem void notifyAboutTextInserted(int startPosition, int endPosition, const String& text); void notifyAboutTextRemoved(int startPosition, int endPosition, const String& text); + void shiftInitOffset(EditData& ed, const PointF& offsetShift); + Align m_align; FrameType m_frameType = FrameType::NO_FRAME; @@ -550,6 +571,7 @@ class TextBase : public EngravingItem VoiceAssignment m_voiceAssignment = VoiceAssignment::ALL_VOICE_IN_INSTRUMENT; DirectionV m_direction = DirectionV::AUTO; AutoOnOff m_centerBetweenStaves = AutoOnOff::AUTO; + bool m_anchorToEndOfPrevious = false; }; inline bool isTextNavigationKey(int key, KeyboardModifiers modifiers) diff --git a/src/engraving/dom/textedit.cpp b/src/engraving/dom/textedit.cpp index 22509f0060d8f..8ecfc6c4a3ca2 100644 --- a/src/engraving/dom/textedit.cpp +++ b/src/engraving/dom/textedit.cpp @@ -25,6 +25,7 @@ #include "iengravingfont.h" #include "types/symnames.h" +#include "anchors.h" #include "mscoreview.h" #include "navigate.h" #include "score.h" @@ -104,7 +105,7 @@ void TextBase::editInsertText(TextCursor* cursor, const String& s) // startEdit //--------------------------------------------------------- -void TextBase::startEdit(EditData& ed) +void TextBase::startEditTextual(EditData& ed) { std::shared_ptr ted = std::make_shared(this); ted->e = this; @@ -134,7 +135,7 @@ void TextBase::startEdit(EditData& ed) // endEdit //--------------------------------------------------------- -void TextBase::endEdit(EditData& ed) +void TextBase::endEditTextual(EditData& ed) { TextEditData* ted = static_cast(ed.getData(this).get()); IF_ASSERT_FAILED(ted && ted->cursor()) { @@ -284,7 +285,7 @@ void TextBase::insertText(EditData& ed, const String& s) score()->undo(new InsertText(cursor, s), &ed); } -bool TextBase::isEditAllowed(EditData& ed) const +bool TextBase::isTextualEditAllowed(EditData& ed) const { // Keep this method closely in sync with TextBase::edit()! @@ -402,7 +403,7 @@ bool TextBase::isEditAllowed(EditData& ed) const // edit //--------------------------------------------------------- -bool TextBase::edit(EditData& ed) +bool TextBase::editTextual(EditData& ed) { // Keep this method closely in sync with TextBase::isEditAllowed()! @@ -741,21 +742,6 @@ void TextBase::movePosition(EditData& ed, TextCursor::MoveOperation op) score()->update(); } -void TextBase::startEditNonTextual(EditData& ed) -{ - EngravingItem::startEdit(ed); -} - -bool TextBase::editNonTextual(EditData& ed) -{ - return EngravingItem::edit(ed); -} - -void TextBase::endEditNonTextual(EditData& ed) -{ - EngravingItem::endEdit(ed); -} - //--------------------------------------------------------- // ChangeText::insertText //--------------------------------------------------------- diff --git a/src/engraving/rendering/score/tdraw.cpp b/src/engraving/rendering/score/tdraw.cpp index 51a951179e376..b6f4432bfc014 100644 --- a/src/engraving/rendering/score/tdraw.cpp +++ b/src/engraving/rendering/score/tdraw.cpp @@ -1184,7 +1184,7 @@ void TDraw::draw(const FiguredBass* item, Painter* painter) if (!item->score()->printing() && item->score()->showUnprintable()) { for (double len : ldata->lineLengths) { if (len > 0) { - painter->setPen(Pen(item->configuration()->formattingColor(), 3)); + painter->setPen(Pen(item->configuration()->frameColor(), 3)); painter->drawLine(0.0, -2, len, -2); // -2: 2 rast. un. above digits } } diff --git a/src/engraving/rendering/score/tlayout.cpp b/src/engraving/rendering/score/tlayout.cpp index c0d3c3e33de56..45a7a20383758 100644 --- a/src/engraving/rendering/score/tlayout.cpp +++ b/src/engraving/rendering/score/tlayout.cpp @@ -2363,14 +2363,15 @@ void TLayout::layoutFiguredBass(const FiguredBass* item, FiguredBass::LayoutData // Items list will be empty in edit mode (see FiguredBass::startEdit). // TODO: consider disabling specific layout in case text style is changed (tid() != TextStyleName::FIGURED_BASS). if (item->items().size() > 0) { + Shape shape; layoutLines(item, ldata, ctx); - ldata->setBbox(0, 0, ldata->lineLength(0), 0); // layout each item and enlarge bbox to include items bboxes for (FiguredBassItem* fit : item->items()) { FiguredBassItem::LayoutData* fildata = fit->mutldata(); layoutFiguredBassItem(fit, fildata, ctx); - ldata->addBbox(fildata->bbox().translated(fit->pos())); + shape.add(fildata->bbox().translated(fit->pos())); } + ldata->setShape(shape); } } diff --git a/src/notation/internal/notationinteraction.cpp b/src/notation/internal/notationinteraction.cpp index a480112fc13f0..ce31e751b5c5b 100644 --- a/src/notation/internal/notationinteraction.cpp +++ b/src/notation/internal/notationinteraction.cpp @@ -44,6 +44,7 @@ #include "engraving/internal/qmimedataadapter.h" #include "engraving/dom/actionicon.h" +#include "engraving/dom/anchors.h" #include "engraving/dom/articulation.h" #include "engraving/dom/barline.h" #include "engraving/dom/box.h" @@ -5609,7 +5610,7 @@ void NotationInteraction::navigateToNearHarmony(MoveDirection direction, bool ne } } - segment = Factory::createSegment(measure, mu::engraving::SegmentType::ChordRest, newTick - measure->tick()); + segment = Factory::createSegment(measure, mu::engraving::SegmentType::TimeTick, newTick - measure->tick()); if (!segment) { LOGD("no prev segment"); return; @@ -5725,8 +5726,7 @@ void NotationInteraction::navigateToHarmony(const Fraction& ticks) startEdit(TranslatableString("undoableAction", "Navigate to chord symbol")); if (!segment || segment->tick() > newTick) { // no ChordRest segment at this tick - segment = Factory::createSegment(measure, mu::engraving::SegmentType::ChordRest, newTick - measure->tick()); - score()->undoAddElement(segment); + segment = EditTimeTickAnchors::createTimeTickAnchor(measure, newTick - measure->tick(), harmony->staffIdx())->segment(); } engraving::track_idx_t track = harmony->track(); @@ -5862,23 +5862,16 @@ void NotationInteraction::navigateToFiguredBass(const Fraction& ticks) nextSegm = nextSegm->next1(mu::engraving::SegmentType::ChordRest); } - bool needAddSegment = false; - if (!nextSegm || nextSegm->tick() > nextSegTick) { // no ChordRest segm at this tick - nextSegm = Factory::createSegment(measure, mu::engraving::SegmentType::ChordRest, nextSegTick - measure->tick()); + nextSegm = EditTimeTickAnchors::createTimeTickAnchor(measure, nextSegTick - measure->tick(), fb->staffIdx())->segment(); if (!nextSegm) { LOGD("figuredBassTicksTab: no next segment"); return; } - needAddSegment = true; } startEdit(TranslatableString("undoableAction", "Navigate to figured bass")); - if (needAddSegment) { - score()->undoAddElement(nextSegm); - } - bool bNew = false; mu::engraving::FiguredBass* fbNew = mu::engraving::FiguredBass::addFiguredBassToSegment(nextSegm, track, ticks, &bNew); if (bNew) { diff --git a/src/notation/view/notationviewinputcontroller.cpp b/src/notation/view/notationviewinputcontroller.cpp index 0ed6ca338ae64..0184ff96a0cc3 100644 --- a/src/notation/view/notationviewinputcontroller.cpp +++ b/src/notation/view/notationviewinputcontroller.cpp @@ -752,14 +752,11 @@ void NotationViewInputController::handleLeftClick(const ClickContext& ctx) INotationSelectionPtr selection = viewInteraction()->selection(); - if (!selection->isRange()) { - if (ctx.hitElement && ctx.hitElement->needStartEditingAfterSelecting()) { - if (ctx.hitElement->hasGrips() && !ctx.hitElement->isImage() && selection->elements().size() == 1) { - viewInteraction()->startEditGrip(ctx.hitElement, ctx.hitElement->gripsCount() > 4 ? Grip::DRAG : Grip::MIDDLE); - } else { - viewInteraction()->startEditElement(ctx.hitElement, false); - } - return; + if (!selection->isRange() && ctx.hitElement && ctx.hitElement->needStartEditingAfterSelecting()) { + if (ctx.hitElement->hasGrips() && !ctx.hitElement->isImage() && selection->elements().size() == 1) { + viewInteraction()->startEditGrip(ctx.hitElement, ctx.hitElement->gripsCount() > 4 ? Grip::DRAG : Grip::MIDDLE); + } else { + viewInteraction()->startEditElement(ctx.hitElement, false); } }