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 e8613936..1aa6b4a4 100644 --- a/src/score/lyricscontext.cpp +++ b/src/score/lyricscontext.cpp @@ -82,47 +82,6 @@ void CALyricsContext::cloneLyricsContextProperties(CALyricsContext* lc) setAssociatedVoice(lc->associatedVoice()); } -/*! - 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. - - \sa CAFunctionMarkContext::repositFunctions(), CAFiguredBassContext::repositFiguredBassMarks(), CAChordNameContext::repositChordNames() -*/ -void CALyricsContext::repositSyllables() -{ - if (associatedVoice()) { - QList noteList = associatedVoice()->getNoteList(); - int i, j; - for (i = 0, j = 0; i < noteList.size() && j < _syllableList.size(); i++, j++) { - if (i > 0 && noteList[i - 1]->timeStart() == noteList[i]->timeStart()) { // chord - i++; - continue; - } - _syllableList[j]->setTimeStart(noteList[i]->timeStart()); - _syllableList[j]->setTimeLength(noteList[i]->timeLength()); - } - int firstEmpty = j; - for (; j < _syllableList.size() && j > 0; j++) { // add syllables at the end, if too much of them exist - if (!_syllableList[j]->text().isEmpty()) - firstEmpty = j + 1; - - _syllableList[j]->setTimeStart(_syllableList[j - 1]->timeStart() + _syllableList[j - 1]->timeLength()); - _syllableList[j]->setTimeLength(256); - } - // remove empty "leftover" syllables from the end - for (j = firstEmpty; j < _syllableList.size() && j > 0; j++) { - delete _syllableList.takeAt(j); - } - - for (; i < noteList.size(); i++) { // add empty syllables at the end, if missing - if (i > 0 && noteList[i]->timeStart() == noteList[i - 1]->timeStart()) // chord - continue; - addEmptySyllable(noteList[i]->timeStart(), noteList[i]->timeLength()); - } - } -} - CAMusElement* CALyricsContext::next(CAMusElement* elt) { if (elt->musElementType() != CAMusElement::Syllable) @@ -164,6 +123,58 @@ bool CALyricsContext::remove(CAMusElement* 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::repositionElements() +{ + if (associatedVoice()) { + QList noteList = associatedVoice()->getNoteList(); + int i, j; + for (i = 0, j = 0; i < noteList.size() && j < _syllableList.size(); i++, j++) { + if (i > 0 && noteList[i - 1]->timeStart() == noteList[i]->timeStart()) { // chord + i++; + continue; + } + _syllableList[j]->setTimeStart(noteList[i]->timeStart()); + _syllableList[j]->setTimeLength(noteList[i]->timeLength()); + } + int firstEmpty = j; + for (; j < _syllableList.size() && j > 0; j++) { // add syllables at the end, if too much of them exist + if (!_syllableList[j]->text().isEmpty()) + firstEmpty = j + 1; + + _syllableList[j]->setTimeStart(_syllableList[j - 1]->timeStart() + _syllableList[j - 1]->timeLength()); + _syllableList[j]->setTimeLength(256); + } + // remove empty "leftover" syllables from the end + for (j = firstEmpty; j < _syllableList.size() && j > 0; j++) { + delete _syllableList.takeAt(j); + } + + for (; i < noteList.size(); i++) { // add empty syllables at the end, if missing + if (i > 0 && noteList[i]->timeStart() == noteList[i - 1]->timeStart()) // chord + continue; + insertEmptyElement(noteList[i]->timeStart()); + } + } +} + /*! 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. @@ -213,23 +224,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. @@ -257,5 +251,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..0b6e3b64 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); @@ -2025,7 +2025,7 @@ void CAMainWin::scoreViewMouseMove(QMouseEvent* e, QPoint coords) \sa CAScoreView::selectAllCurBar() */ -void CAMainWin::scoreViewDoubleClick(QMouseEvent*, const QPoint) +void CAMainWin::scoreViewDoubleClick(QMouseEvent* e, const QPoint) { if (mode() == EditMode) { CAScoreView* c = static_cast(sender()); @@ -2038,12 +2038,21 @@ 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()) { + CAMusElement *newElt = elt->context()->insertEmptyElement(elt->timeStart()); + elt->context()->repositionElements(); + if (newElt) { + rebuildUI(elt->context()->sheet(), true); + dElt = currentScoreView()->findMElement(newElt); + c->clearSelection(); + c->addToSelection(dElt); + } + } + c->createTextEdit(dElt); } else { c->selectAllCurBar(); + c->repaint(); } - - c->repaint(); } } @@ -2455,7 +2464,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()) { @@ -3755,7 +3764,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 +5714,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 +5729,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 +5739,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 +5749,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 +5813,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 +5928,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/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 //