From b19a1d47d4211347a33e0a4e9a92c8ee4858a972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matev=C5=BE=20Jekovec?= Date: Mon, 16 Jan 2023 12:17:54 +0100 Subject: [PATCH 1/3] mainwin: Add support for shift+dblClick to insert syllable or chord --- src/core/muselementfactory.cpp | 27 +++--- src/import/lilypondimport.cpp | 2 +- src/score/chordnamecontext.cpp | 25 ++---- src/score/chordnamecontext.h | 4 +- src/score/context.cpp | 17 ++++ src/score/context.h | 3 + src/score/figuredbasscontext.cpp | 131 ++++++++++++++---------------- src/score/figuredbasscontext.h | 4 +- src/score/functionmarkcontext.cpp | 32 ++++---- src/score/functionmarkcontext.h | 5 +- src/score/lyricscontext.cpp | 124 ++++++++++++++-------------- src/score/lyricscontext.h | 5 +- src/score/staff.cpp | 5 ++ src/score/staff.h | 2 + src/ui/mainwin.cpp | 72 ++++++++++------ src/ui/mainwin.h | 1 + src/widgets/scoreview.cpp | 10 +++ src/widgets/scoreview.h | 1 + 18 files changed, 253 insertions(+), 217 deletions(-) diff --git a/src/core/muselementfactory.cpp b/src/core/muselementfactory.cpp index 60785899..4fb9e5ec 100644 --- a/src/core/muselementfactory.cpp +++ b/src/core/muselementfactory.cpp @@ -263,18 +263,18 @@ bool CAMusElementFactory::configureNote(int pitch, if (voice->lastNote() == mpoMusElement) { // note was appended, reposition elements in dependent contexts accordingly for (CALyricsContext* lc : voice->lyricsContextList()) { - lc->repositSyllables(); + lc->repositionElements(); } for (CAContext* context : voice->staff()->sheet()->contextList()) { switch (context->contextType()) { case CAContext::FunctionMarkContext: - static_cast(context)->repositFunctions(); + static_cast(context)->repositionElements(); break; case CAContext::FiguredBassContext: - static_cast(context)->repositFiguredBassMarks(); + static_cast(context)->repositionElements(); break; case CAContext::ChordNameContext: - static_cast(context)->repositChordNames(); + static_cast(context)->repositionElements(); break; default: break; @@ -283,21 +283,16 @@ bool CAMusElementFactory::configureNote(int pitch, } else { // note was inserted somewhere inbetween, insert empty element in dependent contexts accordingly for (CALyricsContext* lc : voice->lyricsContextList()) { - lc->addEmptySyllable(mpoMusElement->timeStart(), mpoMusElement->timeLength()); + lc->insertEmptyElement(mpoMusElement->timeStart()); + lc->repositionElements(); } for (CAContext* context : voice->staff()->sheet()->contextList()) { switch (context->contextType()) { case CAContext::FunctionMarkContext: - static_cast(context)->addEmptyFunction( - mpoMusElement->timeStart(), mpoMusElement->timeLength()); - break; case CAContext::FiguredBassContext: - static_cast(context)->addEmptyFiguredBassMark( - mpoMusElement->timeStart(), mpoMusElement->timeLength()); - break; case CAContext::ChordNameContext: - static_cast(context)->addEmptyChordName( - mpoMusElement->timeStart(), mpoMusElement->timeLength()); + context->insertEmptyElement(mpoMusElement->timeStart()); + context->repositionElements(); break; default: break; @@ -483,12 +478,14 @@ bool CAMusElementFactory::configureRest(CAVoice* voice, CAMusElement* right) removeMusElem(true); else { for (CALyricsContext* lc : voice->lyricsContextList()) { - lc->repositSyllables(); + lc->repositionElements(); } for (CAContext* context : voice->staff()->sheet()->contextList()) { switch (context->contextType()) { + case CAContext::FunctionMarkContext: + case CAContext::FiguredBassContext: case CAContext::ChordNameContext: - static_cast(context)->repositChordNames(); + static_cast(context)->repositionElements(); break; default: break; diff --git a/src/import/lilypondimport.cpp b/src/import/lilypondimport.cpp index 18e8d7e6..5fb718f4 100644 --- a/src/import/lilypondimport.cpp +++ b/src/import/lilypondimport.cpp @@ -378,7 +378,7 @@ CALyricsContext* CALilyPondImport::importLyricsContextImpl() lc->addSyllable(lastSyllable = new CASyllable(text, false, false, lc, timeSDummy, 0)); } } - lc->repositSyllables(); // sets syllables timeStarts and timeLengths + lc->repositionElements(); // sets syllables timeStarts and timeLengths return lc; } diff --git a/src/score/chordnamecontext.cpp b/src/score/chordnamecontext.cpp index cbbb131a..765fecf0 100644 --- a/src/score/chordnamecontext.cpp +++ b/src/score/chordnamecontext.cpp @@ -26,7 +26,7 @@ CAChordNameContext::CAChordNameContext(QString name, CASheet* sheet) : CAContext(name, sheet) { setContextType(ChordNameContext); - repositChordNames(); + repositionElements(); } CAChordNameContext::~CAChordNameContext() @@ -52,27 +52,20 @@ void CAChordNameContext::addChordName(CAChordName* m, bool replace) } } -/*! - Inserts an empty chord name and shifts the chord names after. - This function is usually called when initializing the context. -*/ -void CAChordNameContext::addEmptyChordName(int timeStart, int timeLength) +CAMusElement* CAChordNameContext::insertEmptyElement(int timeStart) { int i; for (i = 0; i < _chordNameList.size() && _chordNameList[i]->timeStart() < timeStart; i++) ; - _chordNameList.insert(i, (new CAChordName(CADiatonicPitch::Undefined, "", this, timeStart, timeLength))); + CAChordName *newChord = new CAChordName(CADiatonicPitch::Undefined, "", this, timeStart, 1); + _chordNameList.insert(i, newChord); for (i++; i < _chordNameList.size(); i++) - _chordNameList[i]->setTimeStart(_chordNameList[i]->timeStart() + timeLength); -} + _chordNameList[i]->setTimeStart(_chordNameList[i]->timeStart() + 1); -/*! - It repositions the existing chord names (sets timeStart and timeLength) one by one according to the playable music - elements above the context. + return newChord; +} - \sa CALyricsContext::repositSyllables(), CAFiguredBassContext::repositFiguredBassMarks(), CAFunctionMarkContext::repositFunctions() -*/ -void CAChordNameContext::repositChordNames() +void CAChordNameContext::repositionElements() { int ts, tl; int curIdx; // contains current position in _chordNameList @@ -86,7 +79,7 @@ void CAChordNameContext::repositChordNames() // add new empty chord names, if playables still exist above if (curIdx == _chordNameList.size()) { - addEmptyChordName(ts, tl); + insertEmptyElement(ts); } // apply timeStart and timeLength to existing chord names diff --git a/src/score/chordnamecontext.h b/src/score/chordnamecontext.h index f59245bf..77d4cb6e 100644 --- a/src/score/chordnamecontext.h +++ b/src/score/chordnamecontext.h @@ -23,13 +23,13 @@ class CAChordNameContext : public CAContext { CAMusElement* next(CAMusElement* elt); CAMusElement* previous(CAMusElement* elt); bool remove(CAMusElement* elt); + CAMusElement *insertEmptyElement(int timeStart); + void repositionElements(); QList& chordNameList() { return _chordNameList; } CAChordName* chordNameAtTimeStart(int timeStart); - void repositChordNames(); void addChordName(CAChordName*, bool replace = true); - void addEmptyChordName(int timeStart, int timeLength); private: QList _chordNameList; diff --git a/src/score/context.cpp b/src/score/context.cpp index efc4ca8b..d1e7577b 100644 --- a/src/score/context.cpp +++ b/src/score/context.cpp @@ -110,3 +110,20 @@ CAContext::~CAContext() \sa CAMusElement::clone(), CADocument::clone() */ + +/*! + \fn CAContext::insertEmptyElement(int timeStart) + Inserts an empty dependent element (syllable, chord name, figured bass mark, function mark) to the context. + After the call the elements need to be repositioned manually (subsequent timeStarts and timeLengths will be out of place). + This function is usually called when initializing the dependent context or inserting a new note. + + \sa CAContext::repositionElements() +*/ + +/*! + \fn CAContext::repositionElements() + Repositions the existing dependent elements (syllables, chord names, figured bass marks, function marks) by setting timeStart and timeLength + one by one according to the playable music it depends on. The order is preserved. + + \sa CAContext::insertEmptyElement(int) +*/ diff --git a/src/score/context.h b/src/score/context.h index c9df0167..2da88f15 100644 --- a/src/score/context.h +++ b/src/score/context.h @@ -35,10 +35,13 @@ class CAContext { CASheet* sheet() { return _sheet; } void setSheet(CASheet* sheet) { _sheet = sheet; } + virtual void clear() = 0; virtual CAMusElement* next(CAMusElement* elt) = 0; virtual CAMusElement* previous(CAMusElement* elt) = 0; virtual bool remove(CAMusElement* elt) = 0; + virtual CAMusElement *insertEmptyElement(int timeStart) = 0; + virtual void repositionElements() = 0; protected: void setContextType(CAContextType t) { _contextType = t; } diff --git a/src/score/figuredbasscontext.cpp b/src/score/figuredbasscontext.cpp index 58c31b65..5e115176 100644 --- a/src/score/figuredbasscontext.cpp +++ b/src/score/figuredbasscontext.cpp @@ -25,7 +25,7 @@ CAFiguredBassContext::CAFiguredBassContext(QString name, CASheet* sheet) : CAContext(name, sheet) { setContextType(FiguredBassContext); - repositFiguredBassMarks(); + repositionElements(); } CAFiguredBassContext::~CAFiguredBassContext() @@ -51,74 +51,6 @@ void CAFiguredBassContext::addFiguredBassMark(CAFiguredBassMark* m, bool replace _figuredBassMarkList[i]->setTimeStart(_figuredBassMarkList[i]->timeStart() + m->timeLength()); } -/*! - Inserts an empty figured bass mark and shifts the marks after. - This function is usually called when initializing the context. -*/ -void CAFiguredBassContext::addEmptyFiguredBassMark(int timeStart, int timeLength) -{ - int i; - for (i = 0; i < _figuredBassMarkList.size() && _figuredBassMarkList[i]->timeStart() < timeStart; i++) - ; - _figuredBassMarkList.insert(i, (new CAFiguredBassMark(this, timeStart, timeLength))); - for (i++; i < _figuredBassMarkList.size(); i++) - _figuredBassMarkList[i]->setTimeStart(_figuredBassMarkList[i]->timeStart() + timeLength); -} - -/*! - Updates timeStarts and timeLength of all figured bass marks according to the chords they belong. - Adds new empty figured bass marks at the end, if needed. - - \sa CALyricsContext::repositSyllables(), CAFunctionMarkContext::repositFunctions(), CAChordNameContext::repositChordNames() - */ -void CAFiguredBassContext::repositFiguredBassMarks() -{ - if (!sheet()) { - return; - } - - QList chord = sheet()->getChord(0); - int fbmIdx = 0; - while (chord.size()) { - int maxTimeStart = chord[0]->timeStart(); - int minTimeEnd = chord[0]->timeEnd(); - bool notes = false; // are notes present in the chord or only rests? - for (int i = 1; i < chord.size(); i++) { - if (chord[i]->musElementType() == CAMusElement::Note) { - notes = true; - } - - if (chord[i]->timeStart() > maxTimeStart) { - maxTimeStart = chord[i]->timeStart(); - } - if (chord[i]->timeEnd() < minTimeEnd) { - minTimeEnd = chord[i]->timeEnd(); - } - } - - // only assign figured bass marks under the notes - if (notes) { - // add new empty figured bass, if none exist - if (fbmIdx == _figuredBassMarkList.size()) { - addEmptyFiguredBassMark(maxTimeStart, minTimeEnd - maxTimeStart); - } - - CAFiguredBassMark* mark = _figuredBassMarkList[fbmIdx]; - mark->setTimeStart(maxTimeStart); - mark->setTimeLength(minTimeEnd - maxTimeStart); - fbmIdx++; - } - - chord = sheet()->getChord(minTimeEnd); - } - - // updated times for the figured bass marks at the end (after the score) - for (; fbmIdx < _figuredBassMarkList.size(); fbmIdx++) { - _figuredBassMarkList[fbmIdx]->setTimeStart(((fbmIdx > 0) ? _figuredBassMarkList[fbmIdx - 1] : _figuredBassMarkList[0])->timeEnd()); - _figuredBassMarkList[fbmIdx]->setTimeLength(CAPlayableLength::Quarter); - } -} - /*! Returns figured bass mark at the given \a time. */ @@ -188,3 +120,64 @@ bool CAFiguredBassContext::remove(CAMusElement* elt) return success; } + +CAMusElement *CAFiguredBassContext::insertEmptyElement(int timeStart) +{ + CAFiguredBassMark *newElt = new CAFiguredBassMark(this, timeStart, 1); + int i; + for (i = 0; i < _figuredBassMarkList.size() && _figuredBassMarkList[i]->timeStart() < timeStart; i++) + ; + _figuredBassMarkList.insert(i, newElt); + for (i++; i < _figuredBassMarkList.size(); i++) + _figuredBassMarkList[i]->setTimeStart(_figuredBassMarkList[i]->timeStart() + 1); + + return newElt; +} + +void CAFiguredBassContext::repositionElements() +{ + if (!sheet()) { + return; + } + + QList chord = sheet()->getChord(0); + int fbmIdx = 0; + while (chord.size()) { + int maxTimeStart = chord[0]->timeStart(); + int minTimeEnd = chord[0]->timeEnd(); + bool notes = false; // are notes present in the chord or only rests? + for (int i = 1; i < chord.size(); i++) { + if (chord[i]->musElementType() == CAMusElement::Note) { + notes = true; + } + + if (chord[i]->timeStart() > maxTimeStart) { + maxTimeStart = chord[i]->timeStart(); + } + if (chord[i]->timeEnd() < minTimeEnd) { + minTimeEnd = chord[i]->timeEnd(); + } + } + + // only assign figured bass marks under the notes + if (notes) { + // add new empty figured bass, if none exist + if (fbmIdx == _figuredBassMarkList.size()) { + insertEmptyElement(maxTimeStart); + } + + CAFiguredBassMark* mark = _figuredBassMarkList[fbmIdx]; + mark->setTimeStart(maxTimeStart); + mark->setTimeLength(minTimeEnd - maxTimeStart); + fbmIdx++; + } + + chord = sheet()->getChord(minTimeEnd); + } + + // updated times for the figured bass marks at the end (after the score) + for (; fbmIdx < _figuredBassMarkList.size(); fbmIdx++) { + _figuredBassMarkList[fbmIdx]->setTimeStart(((fbmIdx > 0) ? _figuredBassMarkList[fbmIdx - 1] : _figuredBassMarkList[0])->timeEnd()); + _figuredBassMarkList[fbmIdx]->setTimeLength(CAPlayableLength::Quarter); + } +} diff --git a/src/score/figuredbasscontext.h b/src/score/figuredbasscontext.h index eb6d8ce8..81dd4973 100644 --- a/src/score/figuredbasscontext.h +++ b/src/score/figuredbasscontext.h @@ -23,13 +23,13 @@ class CAFiguredBassContext : public CAContext { CAMusElement* next(CAMusElement* elt); CAMusElement* previous(CAMusElement* elt); bool remove(CAMusElement* elt); + CAMusElement *insertEmptyElement(int timeStart); + void repositionElements(); QList& figuredBassMarkList() { return _figuredBassMarkList; } CAFiguredBassMark* figuredBassMarkAtTimeStart(int timeStart); - void repositFiguredBassMarks(); void addFiguredBassMark(CAFiguredBassMark*, bool replace = true); - void addEmptyFiguredBassMark(int timeStart, int timeLength); private: QList _figuredBassMarkList; diff --git a/src/score/functionmarkcontext.cpp b/src/score/functionmarkcontext.cpp index 889841c1..2e063cf8 100644 --- a/src/score/functionmarkcontext.cpp +++ b/src/score/functionmarkcontext.cpp @@ -26,7 +26,7 @@ CAFunctionMarkContext::CAFunctionMarkContext(const QString name, CASheet* sheet) { _contextType = CAContext::FunctionMarkContext; - repositFunctions(); + repositionElements(); } CAFunctionMarkContext::~CAFunctionMarkContext() @@ -101,16 +101,21 @@ bool CAFunctionMarkContext::remove(CAMusElement* elt) return _functionMarkList.removeAll(static_cast(elt)); } -/*! - It repositions the functions (sets timeStart and timeLength) one by one according to the chords - above the context. +CAMusElement *CAFunctionMarkContext::insertEmptyElement(int timeStart) { + CAFunctionMark *newElt = new CAFunctionMark(CAFunctionMark::Undefined, false, CADiatonicKey("C"), this, timeStart, 1); + addFunctionMark(newElt, false); - If two functions contain the same timeStart, they are treated as modulation and will contain - the same timeStart after reposition is done as well! + return newElt; +} - \sa CALyricsContext::repositSyllables(), CAFiguredBassContext::repositFiguredBassMarks(), CAChordNameContext::repositChordNames() +/*! + It repositions the functions (sets timeStart and timeLength) one by one according to the chords + above the context. + + If two functions contain the same timeStart, they are treated as modulation and will contain + the same timeStart after reposition is done as well! */ -void CAFunctionMarkContext::repositFunctions() +void CAFunctionMarkContext::repositionElements() { int ts, tl; int curIdx; // contains current position in _functionMarkList @@ -123,7 +128,7 @@ void CAFunctionMarkContext::repositFunctions() tl = chord[i]->timeLength(); if (curIdx == _functionMarkList.size()) { // add new empty functions, if chords still exist - addEmptyFunction(ts, tl); + insertEmptyElement(ts); curIdx++; } @@ -135,15 +140,6 @@ void CAFunctionMarkContext::repositFunctions() } } -/*! - Adds an undefined function mark (uses for empty function marks when only function mark context exists and no actual - functions added). -*/ -void CAFunctionMarkContext::addEmptyFunction(int timeStart, int timeLength) -{ - addFunctionMark(new CAFunctionMark(CAFunctionMark::Undefined, false, CADiatonicKey("C"), this, timeStart, timeLength), false); -} - /*! Returns the function marks at the exact given \a timeStart. This function is usually called to determine the number of possible modulations of the diff --git a/src/score/functionmarkcontext.h b/src/score/functionmarkcontext.h index 546b4303..7c61a1ae 100644 --- a/src/score/functionmarkcontext.h +++ b/src/score/functionmarkcontext.h @@ -25,14 +25,13 @@ class CAFunctionMarkContext : public CAContext { inline const QList& functionMarkList() { return _functionMarkList; } QList functionMarkAt(int timeStart); void addFunctionMark(CAFunctionMark* mark, bool replace = true); - void addEmptyFunction(int timeStart, int timeLength); - - void repositFunctions(); void clear(); CAMusElement* next(CAMusElement* elt); CAMusElement* previous(CAMusElement* elt); bool remove(CAMusElement* elt); + CAMusElement *insertEmptyElement(int timeStart); + void repositionElements(); private: QList _functionMarkList; diff --git a/src/score/lyricscontext.cpp b/src/score/lyricscontext.cpp index 364d4109..f90f74d7 100644 --- a/src/score/lyricscontext.cpp +++ b/src/score/lyricscontext.cpp @@ -82,14 +82,66 @@ void CALyricsContext::cloneLyricsContextProperties(CALyricsContext* lc) setAssociatedVoice(lc->associatedVoice()); } +CAMusElement* CALyricsContext::next(CAMusElement* elt) +{ + if (elt->musElementType() != CAMusElement::Syllable) + return nullptr; + + int i = _syllableList.indexOf(static_cast(elt)); + if (i != -1 && ++i < _syllableList.size()) + return _syllableList[i]; + else + return nullptr; +} + +CAMusElement* CALyricsContext::previous(CAMusElement* elt) +{ + if (elt->musElementType() != CAMusElement::Syllable) + return nullptr; + + int i = _syllableList.indexOf(static_cast(elt)); + if (i != -1 && --i > -1) + return _syllableList[i]; + else + return nullptr; +} + /*! - Keeps the content and order of the syllables, but changes startTimes and lengths according to the notes in associatedVoice. - This function is usually called when associatedVoice is changed or the whole lyricsContext is initialized for the first time. - If the notes and syllables aren't synchronized (too little syllables for notes) it adds empty syllables. + Removes the given syllable from the list. +*/ +bool CALyricsContext::remove(CAMusElement* elt) +{ + if (!elt || elt->musElementType() != CAMusElement::Syllable) + return false; - \sa CAFunctionMarkContext::repositFunctions(), CAFiguredBassContext::repositFiguredBassMarks(), CAChordNameContext::repositChordNames() + bool success = false; + success = _syllableList.removeAll(static_cast(elt)); + + if (success) + delete elt; + + return success; +} + +CAMusElement *CALyricsContext::insertEmptyElement(int timeStart) +{ + int i; + for (i = 0; i < _syllableList.size() && _syllableList[i]->timeStart() < timeStart; i++) + ; + CASyllable *newSyl = new CASyllable("", ((i > 0) ? (_syllableList[i - 1]->hyphenStart()) : (false)), ((i > 0) ? (_syllableList[i - 1]->melismaStart()) : (false)), this, timeStart, 1); + _syllableList.insert(i, newSyl); + for (i++; i < _syllableList.size(); i++) + _syllableList[i]->setTimeStart(_syllableList[i]->timeStart() + 1); + + return newSyl; +} + +/*! + Keeps the content and order of the syllables, but changes startTimes and lengths according to the notes in associatedVoice. + This function is usually called when associatedVoice is changed or the whole lyricsContext is initialized for the first time. + If the notes and syllables aren't synchronized (too little syllables for notes) it adds empty syllables. */ -void CALyricsContext::repositSyllables() +void CALyricsContext::repositionElements() { if (associatedVoice()) { QList noteList = associatedVoice()->getNoteList(); @@ -128,52 +180,11 @@ void CALyricsContext::repositSyllables() if (!noteList[i]->isFirstInChord()) { // skip until the first note in the chord continue; } - addEmptySyllable(noteList[i]->timeStart(), noteList[i]->timeLength()); + insertEmptyElement(noteList[i]->timeStart()); } } } -CAMusElement* CALyricsContext::next(CAMusElement* elt) -{ - if (elt->musElementType() != CAMusElement::Syllable) - return nullptr; - - int i = _syllableList.indexOf(static_cast(elt)); - if (i != -1 && ++i < _syllableList.size()) - return _syllableList[i]; - else - return nullptr; -} - -CAMusElement* CALyricsContext::previous(CAMusElement* elt) -{ - if (elt->musElementType() != CAMusElement::Syllable) - return nullptr; - - int i = _syllableList.indexOf(static_cast(elt)); - if (i != -1 && --i > -1) - return _syllableList[i]; - else - return nullptr; -} - -/*! - Removes the given syllable from the list. -*/ -bool CALyricsContext::remove(CAMusElement* elt) -{ - if (!elt || elt->musElementType() != CAMusElement::Syllable) - return false; - - bool success = false; - success = _syllableList.removeAll(static_cast(elt)); - - if (success) - delete elt; - - return success; -} - /*! Removes the syllable at the given \a timeStart and updates the timeStarts for syllables after it. This function is usually called when removing the note. @@ -223,23 +234,6 @@ bool CALyricsContext::addSyllable(CASyllable* syllable, bool replace) return true; } -/*! - Adds an empty syllable to the context. - This function is usually called when initializing the lyrics context - or inserting a new note. -*/ -bool CALyricsContext::addEmptySyllable(int timeStart, int timeLength) -{ - int i; - for (i = 0; i < _syllableList.size() && _syllableList[i]->timeStart() < timeStart; i++) - ; - _syllableList.insert(i, (new CASyllable("", ((i > 0) ? (_syllableList[i - 1]->hyphenStart()) : (false)), ((i > 0) ? (_syllableList[i - 1]->melismaStart()) : (false)), this, timeStart, timeLength))); - for (i++; i < _syllableList.size(); i++) - _syllableList[i]->setTimeStart(_syllableList[i]->timeStart() + timeLength); - - return true; -} - /*! Finds the syllable with exactly the given \a timeStart or Null, if such a syllables doesn't exist. @@ -267,5 +261,5 @@ void CALyricsContext::setAssociatedVoice(CAVoice* v) v->addLyricsContext(this); _associatedVoice = v; - repositSyllables(); + repositionElements(); } diff --git a/src/score/lyricscontext.h b/src/score/lyricscontext.h index 9577b3f0..e4a064d8 100644 --- a/src/score/lyricscontext.h +++ b/src/score/lyricscontext.h @@ -24,16 +24,15 @@ class CALyricsContext : public CAContext { CALyricsContext* clone(CASheet* s); void cloneLyricsContextProperties(CALyricsContext*); - void repositSyllables(); - CAMusElement* next(CAMusElement*); CAMusElement* previous(CAMusElement*); bool remove(CAMusElement*); + CAMusElement *insertEmptyElement(int timeStart); + void repositionElements(); void clear(); inline const QList& syllableList() { return _syllableList; } bool addSyllable(CASyllable*, bool replace = true); - bool addEmptySyllable(int timeStart, int timeLength); // void removeSyllable( CASyllable* s ) { _syllableList.removeAll(s); } CASyllable* removeSyllableAtTimeStart(int timeStart); CASyllable* syllableAtTimeStart(int timeStart); diff --git a/src/score/staff.cpp b/src/score/staff.cpp index d660a58a..5732cbcc 100644 --- a/src/score/staff.cpp +++ b/src/score/staff.cpp @@ -260,6 +260,11 @@ bool CAStaff::remove(CAMusElement* elt, bool updateSignTimes) return voiceList()[0]->remove(elt, updateSignTimes); } +CAMusElement *CAStaff::insertEmptyElement(int timeStart) +{ + return nullptr; // N/A +} + /*! Returns the first voice with the given \a name or Null, if such a voice doesn't exist. */ diff --git a/src/score/staff.h b/src/score/staff.h index 1d8f15b8..e10fd415 100644 --- a/src/score/staff.h +++ b/src/score/staff.h @@ -43,6 +43,8 @@ class CAStaff : public CAContext { CAMusElement* previous(CAMusElement* elt); bool remove(CAMusElement* elt, bool updateSignTimes); bool remove(CAMusElement* elt) { return remove(elt, true); } + CAMusElement *insertEmptyElement(int timeStart); + void repositionElements() {} int lastTimeEnd(); QList getEltByType(CAMusElement::CAMusElementType type, int startTime); diff --git a/src/ui/mainwin.cpp b/src/ui/mainwin.cpp index bdeb3cc4..8c24f45e 100644 --- a/src/ui/mainwin.cpp +++ b/src/ui/mainwin.cpp @@ -1744,7 +1744,7 @@ void CAMainWin::scoreViewMousePress(QMouseEvent* e, const QPoint coords) v->clearSelection(); v->addToSelection(newlySelectedElement = l[0]); // if the previous selection was not a single element or if the new list doesn't contain the selection set the first element in the available list to the selection } else { - if (e->modifiers() == Qt::ShiftModifier && v->selection().size()) { + if (e->modifiers() == Qt::ShiftModifier && v->selection().size() && !v->clickTimerActivated()) { v->removeFromSelection(l[0]); // shift used on an already selected element - toggle selection } else { idx = (v->selection().size() ? l.indexOf(v->selection().front()) : -1); @@ -1885,9 +1885,10 @@ void CAMainWin::scoreViewMousePress(QMouseEvent* e, const QPoint coords) uiEditMode->toggle(); v->repaint(); break; - } else - // Insert music element - if (uiInsertPlayable->isChecked()) { + } + + // Insert playable music element + if (uiInsertPlayable->isChecked()) { // Add Note/Rest if (e->button() == Qt::RightButton && musElementFactory()->musElementType() == CAMusElement::Note) // place a rest when using right mouse button and note insertion is selected @@ -1904,7 +1905,7 @@ void CAMainWin::scoreViewMousePress(QMouseEvent* e, const QPoint coords) musElementFactory()->setMusElementType(CAMusElement::Note); // Insert Syllable, Text, or ChordName - if (!v->selection().isEmpty() && (uiInsertSyllable->isChecked() || uiInsertChordName->isChecked() || (uiMarkType->isChecked() && success && (musElementFactory()->markType() == CAMark::Text || musElementFactory()->markType() == CAMark::BookMark)))) { + if (!v->selection().isEmpty() && success && (uiInsertSyllable->isChecked() || uiInsertChordName->isChecked() || (uiMarkType->isChecked() && (musElementFactory()->markType() == CAMark::Text || musElementFactory()->markType() == CAMark::BookMark)))) { v->createTextEdit(v->selection().front()); } else { v->removeTextEdit(); @@ -2025,7 +2026,7 @@ void CAMainWin::scoreViewMouseMove(QMouseEvent* e, QPoint coords) \sa CAScoreView::selectAllCurBar() */ -void CAMainWin::scoreViewDoubleClick(QMouseEvent*, const QPoint) +void CAMainWin::scoreViewDoubleClick(QMouseEvent* e, const QPoint p) { if (mode() == EditMode) { CAScoreView* c = static_cast(sender()); @@ -2038,12 +2039,18 @@ void CAMainWin::scoreViewDoubleClick(QMouseEvent*, const QPoint) } if (elt && (elt->musElementType() == CAMusElement::Syllable || elt->musElementType() == CAMusElement::ChordName || (elt->musElementType() == CAMusElement::Mark && (static_cast(elt)->markType() == CAMark::Text || static_cast(elt)->markType() == CAMark::BookMark)))) { - c->createTextEdit(dElt); + if (e->modifiers()==Qt::ShiftModifier && elt->context() && (elt->musElementType() == CAMusElement::Syllable || elt->musElementType() == CAMusElement::ChordName)) { + CAMusElement::CAMusElementType t = musElementFactory()->musElementType(); + musElementFactory()->setMusElementType(elt->musElementType()); + insertMusElementAt(p, c); + musElementFactory()->setMusElementType(t); + dElt = (c->selection().size()?c->selection()[0]:dElt); + } + c->createTextEdit(dElt); } else { c->selectAllCurBar(); + c->repaint(); } - - c->repaint(); } } @@ -2455,7 +2462,7 @@ void CAMainWin::scoreViewKeyPress(QKeyEvent* e) } for (int j = 0; j < p->voice()->lyricsContextList().size(); j++) { // reposit syllables - p->voice()->lyricsContextList().at(j)->repositSyllables(); + p->voice()->lyricsContextList().at(j)->repositionElements(); } if (CACanorus::settings()->useNoteChecker()) { @@ -2938,10 +2945,20 @@ bool CAMainWin::insertMusElementAt(const QPoint coords, CAScoreView* v) } break; } - case CAMusElement::MidiNote: case CAMusElement::Syllable: - case CAMusElement::Tuplet: case CAMusElement::ChordName: + if (drawableContext->context()->contextType() == CAContext::LyricsContext || drawableContext->context()->contextType() == CAContext::ChordNameContext) { + CADrawableMusElement *dLeft = v->nearestLeftElement(coords.x(), coords.y(), drawableContext); + CAMusElement *newElt = drawableContext->context()->insertEmptyElement(dLeft ? dLeft->musElement()->timeStart() : 0); + if (newElt) { + newElt->context()->repositionElements(); + musElementFactory()->setMusElement(newElt); + success = true; + } + } + break; + case CAMusElement::MidiNote: + case CAMusElement::Tuplet: case CAMusElement::Undefined: qDebug() << "Warning: CAMainWin::insertMusElementAt - Unhandled Element" << musElementFactory()->musElementType(); break; @@ -3682,6 +3699,7 @@ void CAMainWin::on_uiInsertPlayable_toggled(bool checked) void CAMainWin::on_uiInsertSyllable_toggled(bool checked) { if (checked) { + musElementFactory()->setMusElementType(CAMusElement::Syllable); setMode(InsertMode); } } @@ -3702,6 +3720,14 @@ void CAMainWin::on_uiInsertFM_toggled(bool checked) } } +void CAMainWin::on_uiInsertChordName_toggled(bool checked) +{ + if (checked) { + musElementFactory()->setMusElementType(CAMusElement::ChordName); + setMode(InsertMode); + } +} + void CAMainWin::on_uiPlayableLength_toggled(bool, int buttonId) { // Read currently selected entry from tool button menu @@ -3755,7 +3781,7 @@ void CAMainWin::on_uiPlayableLength_toggled(bool, int buttonId) } for (int j = 0; j < p->voice()->lyricsContextList().size(); j++) { // reposit syllables - p->voice()->lyricsContextList().at(j)->repositSyllables(); + p->voice()->lyricsContextList().at(j)->repositionElements(); } } } @@ -5705,14 +5731,14 @@ void CAMainWin::deleteSelection(CAScoreView* v, bool deleteSyllables, bool delet p->voice()->remove(p, true); for (int j = 0; j < p->voice()->lyricsContextList().size(); j++) { - p->voice()->lyricsContextList().at(j)->repositSyllables(); + p->voice()->lyricsContextList().at(j)->repositionElements(); } delete p; } else if ((*i)->musElementType() == CAMusElement::Syllable) { if (deleteSyllables) { CALyricsContext* lc = static_cast((*i)->context()); (*i)->context()->remove(*i); // actually removes the syllable if SHIFT is pressed - lc->repositSyllables(); + lc->repositionElements(); } else { static_cast(*i)->clear(); // only clears syllable's text } @@ -5720,7 +5746,7 @@ void CAMainWin::deleteSelection(CAScoreView* v, bool deleteSyllables, bool delet if (deleteSyllables) { CAChordNameContext* cc = static_cast((*i)->context()); (*i)->context()->remove(*i); // actually removes the chord if SHIFT is pressed - cc->repositChordNames(); + cc->repositionElements(); } else { static_cast(*i)->clear(); // only clears the chord pitch/modifiers } @@ -5730,7 +5756,7 @@ void CAMainWin::deleteSelection(CAScoreView* v, bool deleteSyllables, bool delet if (deleteSyllables && fbm->numbers().size() == numbersToDelete[fbm].size()) { (*i)->context()->remove(*i); // actually removes the function if SHIFT is pressed - fbc->repositFiguredBassMarks(); + fbc->repositionElements(); } else { for (int j = 0; j < numbersToDelete[fbm].size(); j++) { fbm->removeNumber(numbersToDelete[fbm][j]); @@ -5740,7 +5766,7 @@ void CAMainWin::deleteSelection(CAScoreView* v, bool deleteSyllables, bool delet if (deleteSyllables) { CAFunctionMarkContext* fmc = static_cast((*i)->context()); (*i)->context()->remove(*i); // actually removes the function if SHIFT is pressed - fmc->repositFunctions(); + fmc->repositionElements(); } else { static_cast(*i)->clear(); // only clears the function } @@ -5804,7 +5830,7 @@ void CAMainWin::pasteAt(const QPoint coords, CAScoreView* v) /* Two cases: * - Some notes were copied together with the lyrics below them: in this case the linked voice would've already been pasted (as the context list is ordered top to bottom), so we find the new voice using voiceMap. * - Lyrics were copied without the notes. If currentContext is a staff, we'll use the current voice. Otherwise lyrics will not be pasted. - */ + */ CAVoice* voice = voiceMap[static_cast(context)->associatedVoice()]; if (!voice && currentContext && currentContext->contextType() == CAContext::Staff) voice = static_cast(currentContext)->voiceList()[(currentContext == v->currentContext()->context()) ? (uiVoiceNum->getRealValue() ? uiVoiceNum->getRealValue() - 1 : uiVoiceNum->getRealValue()) : 1]; // That is, if the currentContext is still the context that the user last clicked before pasting, use the current voice number. Otherwise, use the first voice. @@ -5919,17 +5945,17 @@ void CAMainWin::pasteAt(const QPoint coords, CAScoreView* v) // FIXME duplicated from CAMusElementFactory::configureNote. if (n && staff->voiceList()[i]->lastNote() != static_cast(cloned)) { for (CALyricsContext* context : staff->voiceList()[i]->lyricsContextList()) - context->addEmptySyllable(cloned->timeStart(), cloned->timeLength()); + context->insertEmptyElement(cloned->timeStart()); for (CAContext* context : currentSheet->contextList()) if (context->contextType() == CAContext::FunctionMarkContext) - static_cast(context)->addEmptyFunction(cloned->timeStart(), cloned->timeLength()); + static_cast(context)->insertEmptyElement(cloned->timeStart()); } } for (CALyricsContext* context : staff->voiceList()[i]->lyricsContextList()) - context->repositSyllables(); + context->repositionElements(); for (CAContext* context : currentSheet->contextList()) if (context->contextType() == CAContext::FunctionMarkContext) - static_cast(context)->repositFunctions(); + static_cast(context)->repositionElements(); } staff->synchronizeVoices(); } else { diff --git a/src/ui/mainwin.h b/src/ui/mainwin.h index 9c0efd7f..94c943ca 100644 --- a/src/ui/mainwin.h +++ b/src/ui/mainwin.h @@ -214,6 +214,7 @@ private slots: void on_uiInsertSyllable_toggled(bool); void on_uiInsertFBM_toggled(bool); void on_uiInsertFM_toggled(bool); + void on_uiInsertChordName_toggled(bool); // View void on_uiFullscreen_toggled(bool); diff --git a/src/widgets/scoreview.cpp b/src/widgets/scoreview.cpp index 2964e9e3..65c907c6 100644 --- a/src/widgets/scoreview.cpp +++ b/src/widgets/scoreview.cpp @@ -1397,6 +1397,16 @@ bool CAScoreView::mouseDragActivated() return qMax(qAbs(_xCursor - _lastMousePressCoords.x()), qAbs(_yCursor - _lastMousePressCoords.y())) * _zoom >= SELECTION_REGION_THRESHOLD; } +/*! + Is click timer still active and waiting for potential double or triple click. + + @return True, if click timer is activated; False otherwise. + */ +bool CAScoreView::clickTimerActivated() +{ + return _clickTimer->isActive(); +} + /*! Processes the wheelEvent(). A new signal is emitted: CAWheelEvent(), which usually gets processed by the parent class then. diff --git a/src/widgets/scoreview.h b/src/widgets/scoreview.h index 0926714b..a2259cc7 100644 --- a/src/widgets/scoreview.h +++ b/src/widgets/scoreview.h @@ -129,6 +129,7 @@ class CAScoreView : public CAView { inline void clearSelectionRegionList() { _selectionRegionList.clear(); } inline CADrawable::CADirection resizeDirection() { return _resizeDirection; } bool mouseDragActivated(); + bool clickTimerActivated(); ///////////////////////////////////////////////////////////////////// // Music elements and contexts query, space calculation and access // From 46322c65fab5532993817510d7fcea2d49bc3b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matev=C5=BE=20Jekovec?= Date: Sun, 19 Nov 2023 18:49:11 +0100 Subject: [PATCH 2/3] mainwin: Preserve Insert toolbar visibility when in Insert mode --- src/ui/mainwin.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ui/mainwin.cpp b/src/ui/mainwin.cpp index 8c24f45e..2c567226 100644 --- a/src/ui/mainwin.cpp +++ b/src/ui/mainwin.cpp @@ -4906,6 +4906,12 @@ void CAMainWin::updateContextToolBar() */ void CAMainWin::updateInsertToolBar() { + if (mode() == InsertMode) { + // Do not change any insert toolbar layout when in Insert mode. Otherwise the user couldn't leave the Insert mode, + // because the specific toggle button may be evicted. + return; + } + if (currentSheet()) { uiNewContext->setVisible(true); uiInsertToolBar->show(); From f5bdf85def9577ba04b5e581e4e9983a70cfa210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matev=C5=BE=20Jekovec?= Date: Tue, 21 Nov 2023 00:31:33 +0100 Subject: [PATCH 3/3] mainwin: Fix scoreview vertical shifting --- src/ui/mainwin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ui/mainwin.cpp b/src/ui/mainwin.cpp index 2c567226..7a98ae5b 100644 --- a/src/ui/mainwin.cpp +++ b/src/ui/mainwin.cpp @@ -633,6 +633,7 @@ void CAMainWin::setupCustomUi() /// \todo When Qt Designer have support for setting the visibility property, do this in Qt Designer already! -Matevz uiPrintToolBar->hide(); uiFileToolBar->hide(); + uiStandardToolBar->setMinimumHeight(48); // Hack to prevent score view shifting when there is QTextEdit in the top toolbar or not. uiStandardToolBar->updateGeometry(); // Insert Toolbar