From 3939c6d36886a54f7e18c07190602b3a0d6342f9 Mon Sep 17 00:00:00 2001 From: Matt McClinch Date: Mon, 15 Feb 2021 06:28:10 -0500 Subject: [PATCH 1/7] Allow beaming across crotchet rests. --- src/libmscore/beam.cpp | 2 +- src/libmscore/layout.cpp | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libmscore/beam.cpp b/src/libmscore/beam.cpp index 59201c851e9b1..94a8136bc3f37 100644 --- a/src/libmscore/beam.cpp +++ b/src/libmscore/beam.cpp @@ -1826,7 +1826,7 @@ void Beam::layout2(std::vector crl, SpannerSegmentType, int frag) for (; i < n; ++i) { ChordRest* c = crl[i]; ChordRest* p = i ? crl[i - 1] : 0; - int l = c->durationType().hooks() - 1; + int l = c->isChord() ? c->durationType().hooks() - 1 : beamLevel; Mode bm = Groups::endBeam(c, p); b32 = (beamLevel >= 1) && (bm == Mode::BEGIN32); diff --git a/src/libmscore/layout.cpp b/src/libmscore/layout.cpp index 15a6c2173358c..454fadf1df590 100644 --- a/src/libmscore/layout.cpp +++ b/src/libmscore/layout.cpp @@ -2735,8 +2735,7 @@ void Score::createBeams(LayoutContext& lc, Measure* measure) if (!beamNoContinue(prevCR->beamMode()) && !pm->lineBreak() && !pm->pageBreak() && !pm->sectionBreak() && lc.prevMeasure - && prevCR->durationType().type() >= TDuration::DurationType::V_EIGHTH - && prevCR->durationType().type() <= TDuration::DurationType::V_1024TH) { + && !(prevCR->isChord() && prevCR->durationType().type() <= TDuration::DurationType::V_QUARTER)) { beam = prevCR->beam(); //a1 = beam ? beam->elements().front() : prevCR; a1 = beam ? nullptr : prevCR; // when beam is found, a1 is no longer required. @@ -2801,7 +2800,7 @@ void Score::createBeams(LayoutContext& lc, Measure* measure) bm = Beam::Mode::NONE; } - if ((cr->durationType().type() <= TDuration::DurationType::V_QUARTER) || (bm == Beam::Mode::NONE)) { + if ((cr->isChord() && cr->durationType().type() <= TDuration::DurationType::V_QUARTER) || (bm == Beam::Mode::NONE)) { bool removeBeam = true; if (beam) { beam->layout1(); From ec5da46cab148f50a8dd7d1fda69b4812cb04fe1 Mon Sep 17 00:00:00 2001 From: Matt McClinch Date: Mon, 15 Feb 2021 07:04:03 -0500 Subject: [PATCH 2/7] Fix #316441: Crash when changing time signature in front of a corrupted measure Resolves: https://musescore.org/en/node/316441. Modified checkRest() so that it returns true on success or false on failure, instead of causing the program to crash if the end of the measure list is reached prematurely. This allows us to safely back out of a track list rewrite in the case of a time signature change in front of a corrupted measure. --- src/libmscore/edit.cpp | 6 ------ src/libmscore/mscore.cpp | 2 ++ src/libmscore/mscore.h | 3 ++- src/libmscore/range.cpp | 21 ++++++++++++++++----- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/libmscore/edit.cpp b/src/libmscore/edit.cpp index 751c096f17f18..1474720d85fa6 100644 --- a/src/libmscore/edit.cpp +++ b/src/libmscore/edit.cpp @@ -1016,12 +1016,6 @@ bool Score::rewriteMeasures(Measure* fm, const Fraction& ns, int staffIdx) break; } } - } else { - // this can be hit for local time signatures as well - // (if we are rewriting all staves, but one has a local time signature) - // TODO: detect error conditions better, have clearer error messages - // and perform necessary fixups - MScore::setError(MsError::TUPLET_CROSSES_BAR); } for (Measure* m = fm1; m; m = m->nextMeasure()) { if (m->first(SegmentType::TimeSig)) { diff --git a/src/libmscore/mscore.cpp b/src/libmscore/mscore.cpp index 10c8120055fb7..b4852f8367101 100644 --- a/src/libmscore/mscore.cpp +++ b/src/libmscore/mscore.cpp @@ -169,6 +169,8 @@ std::vector MScore::errorList { { MsError::DEST_NO_CR, "p7", QT_TRANSLATE_NOOP("error", "Destination is not a chord or rest") }, { MsError::CANNOT_CHANGE_LOCAL_TIMESIG, "l1", QT_TRANSLATE_NOOP("error", "Cannot change local time signature:\nMeasure is not empty") }, + { MsError::CORRUPTED_MEASURE, "c1", QT_TRANSLATE_NOOP("error", + "Cannot change time signature in front of a corrupted measure") }, }; MsError MScore::_error { MsError::MS_NO_ERROR }; diff --git a/src/libmscore/mscore.h b/src/libmscore/mscore.h index e53322bab563a..ac6ed62d6e574 100644 --- a/src/libmscore/mscore.h +++ b/src/libmscore/mscore.h @@ -1,4 +1,4 @@ -//============================================================================= +//============================================================================= // MuseScore // Music Composition & Notation // @@ -264,6 +264,7 @@ enum class MsError { NO_MIME, DEST_NO_CR, CANNOT_CHANGE_LOCAL_TIMESIG, + CORRUPTED_MEASURE, }; /// \cond PLUGIN_API \private \endcond diff --git a/src/libmscore/range.cpp b/src/libmscore/range.cpp index e15c5153b8f3f..549bd344397d6 100644 --- a/src/libmscore/range.cpp +++ b/src/libmscore/range.cpp @@ -355,16 +355,18 @@ void TrackList::read(const Segment* fs, const Segment* es) // checkRest //--------------------------------------------------------- -static void checkRest(Fraction& rest, Measure*& m, const Fraction& d) +static bool checkRest(Fraction& rest, Measure*& m, const Fraction& d) { if (rest.isZero()) { if (m->nextMeasure()) { m = m->nextMeasure(); rest = m->ticks(); } else { - qFatal("premature end of measure list, rest %d/%d", d.numerator(), d.denominator()); + qWarning("premature end of measure list, rest %d/%d", d.numerator(), d.denominator()); + return false; } } + return true; } //--------------------------------------------------------- @@ -484,7 +486,10 @@ bool TrackList::write(Score* score, const Fraction& tick) const for (Element* e : *this) { if (e->isDurationElement()) { Fraction duration = toDurationElement(e)->ticks(); - checkRest(remains, m, duration); // go to next measure, if necessary + if (!checkRest(remains, m, duration)) { // go to next measure, if necessary + MScore::setError(MsError::CORRUPTED_MEASURE); + return false; + } if (duration > remains && e->isTuplet()) { // experimental: allow tuplet split in the middle if (duration != remains * 2) { @@ -516,7 +521,10 @@ bool TrackList::write(Score* score, const Fraction& tick) const } else if (e->isChordRest()) { Fraction du = qMin(remains, duration); std::vector dl = toDurationList(du, e->isChord()); - Q_ASSERT(!dl.empty()); + if (dl.empty()) { + MScore::setError(MsError::CORRUPTED_MEASURE); + return false; + } for (const TDuration& k : dl) { segment = m->undoGetSegmentR(SegmentType::ChordRest, m->ticks() - remains); ChordRest* cr = toChordRest(e->clone()); @@ -559,7 +567,10 @@ bool TrackList::write(Score* score, const Fraction& tick) const } firstpart = false; if (duration > Fraction(0,1)) { - checkRest(remains, m, duration); // go to next measure, if necessary + if (!checkRest(remains, m, duration)) { // go to next measure, if necessary + MScore::setError(MsError::CORRUPTED_MEASURE); + return false; + } } } } else if (e->isBarLine()) { From 1d0d79525ae8c702a4319b7608891ac698f160d3 Mon Sep 17 00:00:00 2001 From: Matt McClinch Date: Mon, 15 Feb 2021 07:09:53 -0500 Subject: [PATCH 3/7] Fix #316797: Deleting a breath/caesura selects the wrong note. Resolves: https://musescore.org/en/node/316797. Since caesuras are placed after the selected note/rest, it makes sense that when the caesura is deleted, we should select the note/rest that precedes the caesura (in the same track). --- src/libmscore/edit.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libmscore/edit.cpp b/src/libmscore/edit.cpp index 1474720d85fa6..572f0e65b819e 100644 --- a/src/libmscore/edit.cpp +++ b/src/libmscore/edit.cpp @@ -3066,6 +3066,14 @@ void Score::cmdDeleteSelection() tick = toMeasureRepeat(e)->firstMeasureOfGroup()->first()->tick(); } else if (e->isSpannerSegment()) { tick = toSpannerSegment(e)->spanner()->tick(); + } else if (e->isBreath()) { + // we want the tick of the ChordRest that precedes the breath mark (in the same track) + for (Segment* s = toBreath(e)->segment()->prev(); s; s = s->prev()) { + if (s->isChordRestType() && s->element(e->track())) { + tick = s->tick(); + break; + } + } } else if (e->parent() && (e->parent()->isSegment() || e->parent()->isChord() || e->parent()->isNote() || e->parent()->isRest())) { tick = e->parent()->tick(); From cceefb01e7c903a9ed785698db82d0ae77ffcf7c Mon Sep 17 00:00:00 2001 From: Matt McClinch Date: Mon, 15 Feb 2021 07:26:26 -0500 Subject: [PATCH 4/7] Fix #316754: Empty rehearsal mark not deleted after entering a line break Resolves: https://musescore.org/en/node/316754. When #4359 attempted to fix https://musescore.org/en/node/278068 (and was shortly followed up by #4364), the end result was that when a newline is inserted into a text block (causing it to split into two text blocks), the second text block would always end with a newline, even if the original text block did not. This is fixed here by setting the second text block's EOL flag to that of the original text block. This is what is attempted by the function that joins two text blocks when a newline is deleted, but that function wasn't getting it right either. So that has been corrected here as well. Taken together, these two errors meant that there would be a blank line at the end of any text element that had ever contained a newline. This blank line would be small, but it would always be present, even if you tried to delete it. Because this blank was always present in multiline text elements, and because it was small enough to not attract much attention, #5881 only added an empty text fragment before a newline, and not after. But now that we do not have to have unwanted newline characters, in order to preserve the font size on a blank line, an empty text fragment may be needed after a newline also. --- src/libmscore/textbase.cpp | 6 ++++++ src/libmscore/textedit.cpp | 8 +++----- vtest/scores/frametext.mscx | 3 +-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/libmscore/textbase.cpp b/src/libmscore/textbase.cpp index 739e2045ea47d..27557245a3c61 100644 --- a/src/libmscore/textbase.cpp +++ b/src/libmscore/textbase.cpp @@ -1856,6 +1856,12 @@ void TextBase::createLayout() if (rows() <= cursor.row()) { _layout.append(TextBlock()); } + + if (cursor.row() < rows()) { + if (_layout[cursor.row()].fragments().size() == 0) { + _layout[cursor.row()].insertEmptyFragmentIfNeeded(&cursor); // an empty fragment may be needed on either side of the newline + } + } } else { if (symState) { sym += c; diff --git a/src/libmscore/textedit.cpp b/src/libmscore/textedit.cpp index 02a87722c421c..114073bdb4cb4 100644 --- a/src/libmscore/textedit.cpp +++ b/src/libmscore/textedit.cpp @@ -571,13 +571,10 @@ void SplitJoinText::join(EditData* ed) t->textBlock(line - 1).fragments().append(*fragmentsList); delete fragmentsList; - int lines = t->rows(); - if (line < lines) { - t->textBlock(line).setEol(eol); - } t->textBlockList().removeAt(line); c.setRow(line - 1); + c.curLine().setEol(eol); c.setColumn(col); c.setFormat(*charFmt); // restore orig. format at new line c.clearSelection(); @@ -592,12 +589,13 @@ void SplitJoinText::split(EditData* ed) { TextBase* t = c.text(); int line = c.row(); + bool eol = c.curLine().eol(); t->setTextInvalid(); t->triggerLayout(); CharFormat* charFmt = c.format(); // take current format t->textBlockList().insert(line + 1, c.curLine().split(c.column(), t->cursorFromEditData(*ed))); - c.curLine().setEol(true); + c.curLine().setEol(eol); c.setRow(line + 1); c.curLine().setEol(true); diff --git a/vtest/scores/frametext.mscx b/vtest/scores/frametext.mscx index 2e9e30f5d255a..3d4eca3de6a80 100644 --- a/vtest/scores/frametext.mscx +++ b/vtest/scores/frametext.mscx @@ -154,8 +154,7 @@ 12 right,center - rightCenter - + rightCenter 12 From 6cec105234948f1bdb0b97184b421a7d2cb6d4e9 Mon Sep 17 00:00:00 2001 From: Matt McClinch Date: Mon, 15 Feb 2021 09:31:18 -0500 Subject: [PATCH 5/7] Fix #305777: Make applying tremolo a toggle operation Resolves: https://musescore.org/en/node/305777. --- src/libmscore/chord.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libmscore/chord.cpp b/src/libmscore/chord.cpp index 5a338dd40020e..f91472747cf75 100644 --- a/src/libmscore/chord.cpp +++ b/src/libmscore/chord.cpp @@ -2759,7 +2759,12 @@ Element* Chord::drop(EditData& data) } } if (tremolo()) { + bool sameType = (e->subtype() == tremolo()->subtype()); score()->undoRemoveElement(tremolo()); + if (sameType) { + delete e; + return 0; + } } e->setParent(this); e->setTrack(track()); From 7a75f0e2d3565cf80b3876f5f7638d8134196df2 Mon Sep 17 00:00:00 2001 From: Matt McClinch Date: Mon, 15 Feb 2021 09:34:20 -0500 Subject: [PATCH 6/7] Fix #316679: Segmentation Fault when opening a file with a missing section break element Resolves: https://musescore.org/en/node/316679. The LayoutBreak class failed to override Element::subtype(), causing a LayoutBreak of type LINE to be considered a match for a LayoutBreak of type SECTION when determining which elements to reuse from a previous incarnation of an MMRest. --- src/libmscore/layoutbreak.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libmscore/layoutbreak.h b/src/libmscore/layoutbreak.h index f55f0a68cc8bb..9fe9607b6ebfb 100644 --- a/src/libmscore/layoutbreak.h +++ b/src/libmscore/layoutbreak.h @@ -56,6 +56,7 @@ class LayoutBreak final : public Element LayoutBreak* clone() const override { return new LayoutBreak(*this); } ElementType type() const override { return ElementType::LAYOUT_BREAK; } + int subtype() const override { return static_cast(_layoutBreakType); } void setLayoutBreakType(Type); Type layoutBreakType() const { return _layoutBreakType; } From 3a0861a35204db8f1e32fb93d76217ca7edaba90 Mon Sep 17 00:00:00 2001 From: Matt McClinch Date: Mon, 15 Feb 2021 09:49:38 -0500 Subject: [PATCH 7/7] Fix #316555: Invisible breath shouldn't impact layout. Resolves: https://musescore.org/en/node/316555. Allows Segment::allElementsInvisible() to return true for breath segments if all elements in the segment are invisible. --- src/libmscore/segment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libmscore/segment.cpp b/src/libmscore/segment.cpp index 319a328a681fa..3d5329b3a342e 100644 --- a/src/libmscore/segment.cpp +++ b/src/libmscore/segment.cpp @@ -1125,7 +1125,7 @@ bool Segment::hasElements(int minTrack, int maxTrack) const bool Segment::allElementsInvisible() const { - if (isType(SegmentType::BarLineType | SegmentType::ChordRest | SegmentType::Breath)) { + if (isType(SegmentType::BarLineType | SegmentType::ChordRest)) { return false; }