From 69f7c3243dbcb6c61dc17e7ef9fd0700b2101ff1 Mon Sep 17 00:00:00 2001 From: szeli1 <143485814+szeli1@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:37:40 +0100 Subject: [PATCH] Fix knob linking / refactor linking (#7883) Closes #7869 This PR aims to fix linking bugs and it aims to make linking code faster. In the future I would like to replace controller and automation code with linked models, so it is essential for this feature to work as efficiently as possible. Before this PR: - AutomatableModels store a list of AutomatableModels that they are linked to. - setValue() and other functions make recursive calls to themself resulting in the #7869 crash. - Each AutomatableModel can unlink from other AutomatableModels, unlinking is the inverse operation to linking. After this PR: - AutomatableModels store a pointer to an other AutomatableModel making a linked list. The end is connected to the first element resulting in a "ring". - setValue() and others are now recursion free, the code runs for every linked model, more efficiently than before. - Each AutomatableModel can NOT unlink form other AutomatableModels, unlinking is NOT the inverse operation to linking. AutomatableModels can unlink themself from the linked list, they can not unlink themself from single models. --------- Co-authored-by: allejok96 --- data/themes/classic/edit_unlink.svg | 76 +++++++ data/themes/default/edit_unlink.svg | 76 +++++++ include/AutomatableModel.h | 66 +++--- include/AutomatableModelView.h | 1 - plugins/Vestige/Vestige.cpp | 4 +- plugins/VstEffect/VstEffectControls.cpp | 4 +- src/core/AutomatableModel.cpp | 245 ++++++++++------------- src/core/LadspaControl.cpp | 14 +- src/core/LinkedModelGroups.cpp | 2 +- src/core/Song.cpp | 19 +- src/gui/AutomatableModelView.cpp | 13 +- src/gui/widgets/FloatModelEditorBase.cpp | 19 +- tests/src/core/AutomatableModelTest.cpp | 20 +- 13 files changed, 338 insertions(+), 221 deletions(-) create mode 100644 data/themes/classic/edit_unlink.svg create mode 100644 data/themes/default/edit_unlink.svg diff --git a/data/themes/classic/edit_unlink.svg b/data/themes/classic/edit_unlink.svg new file mode 100644 index 000000000..15526dea4 --- /dev/null +++ b/data/themes/classic/edit_unlink.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + diff --git a/data/themes/default/edit_unlink.svg b/data/themes/default/edit_unlink.svg new file mode 100644 index 000000000..15526dea4 --- /dev/null +++ b/data/themes/default/edit_unlink.svg @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 0f05ea343..f7d9b470e 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -77,8 +77,6 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject { Q_OBJECT public: - using AutoModelVector = std::vector; - enum class ScaleType { Linear, @@ -150,22 +148,26 @@ public: template inline T value( int frameOffset = 0 ) const { - if (m_controllerConnection) + // TODO + // The `m_value` should only be updated whenever the Controller value changes, + // instead of the Model calling `controller->currentValue()` every time. + // This becomes even worse in the case of linked Models, where it has to + // loop through the list of all links. + + if (m_useControllerValue) { - if (!m_useControllerValue) - { - return castValue(m_value); - } - else + if (m_controllerConnection) { return castValue(controllerValue(frameOffset)); } + for (auto next = m_nextLink; next != this; next = next->m_nextLink) + { + if (next->controllerConnection() && next->useControllerValue()) + { + return castValue(fittedValue(next->controllerValue(frameOffset))); + } + } } - else if (hasLinkedModels()) - { - return castValue( controllerValue( frameOffset ) ); - } - return castValue( m_value ); } @@ -211,8 +213,7 @@ public: void setInitValue( const float value ); - void setAutomatedValue( const float value ); - void setValue( const float value ); + void setValue(const float value, const bool isAutomated = false); void incValue( int steps ) { @@ -249,11 +250,10 @@ public: m_centerValue = centerVal; } - //! link @p m1 and @p m2, let @p m1 take the values of @p m2 - static void linkModels( AutomatableModel* m1, AutomatableModel* m2 ); - static void unlinkModels( AutomatableModel* m1, AutomatableModel* m2 ); - - void unlinkAllModels(); + //! link this to @p model, copying the value from @p model + void linkToModel(AutomatableModel* model); + //! @return number of other models linked to this + size_t countLinks() const; /** * @brief Saves settings (value, automation links and controller connections) of AutomatableModel into @@ -276,9 +276,9 @@ public: virtual QString displayValue( const float val ) const = 0; - bool hasLinkedModels() const + bool isLinked() const { - return !m_linkedModels.empty(); + return m_nextLink != this; } // a way to track changed values in the model and avoid using signals/slots - useful for speed-critical code. @@ -311,13 +311,14 @@ public: s_periodCounter = 0; } - bool useControllerValue() + bool useControllerValue() const { return m_useControllerValue; } public slots: virtual void reset(); + void unlink(); void unlinkControllerConnection(); void setUseControllerValue(bool b = true); @@ -367,9 +368,15 @@ private: loadSettings( element, "value" ); } - void linkModel( AutomatableModel* model ); - void unlinkModel( AutomatableModel* model ); + void setValueInternal(const float value); + //! linking is stored in a linked list ring + //! @return the model whose `m_nextLink` is `this`, + //! or `this` if there are no linked models + AutomatableModel* getLastLinkedModel() const; + //! @return true if the `model` is in the linked list + bool isLinkedToModel(AutomatableModel* model) const; + //! @brief Scales @value from linear to logarithmic. //! Value should be within [0,1] template T logToLinearScale( T value ) const; @@ -389,16 +396,15 @@ private: float m_centerValue; bool m_valueChanged; - - // currently unused? - float m_oldValue; - int m_setValueDepth; + float m_oldValue; //!< used by valueBuffer for interpolation // used to determine if step size should be applied strictly (ie. always) // or only when value set from gui (default) bool m_hasStrictStepSize; - AutoModelVector m_linkedModels; + //! an `AutomatableModel` can be linked together with others in a linked list + //! the list has no end, the last model is connected to the first forming a ring + AutomatableModel* m_nextLink; //! NULL if not appended to controller, otherwise connection info diff --git a/include/AutomatableModelView.h b/include/AutomatableModelView.h index 12b2e4d49..2d4a88ac0 100644 --- a/include/AutomatableModelView.h +++ b/include/AutomatableModelView.h @@ -98,7 +98,6 @@ public slots: void execConnectionDialog(); void removeConnection(); void editSongGlobalAutomation(); - void unlinkAllModels(); void removeSongGlobalAutomation(); private slots: diff --git a/plugins/Vestige/Vestige.cpp b/plugins/Vestige/Vestige.cpp index a71f5efd8..34fd431cd 100644 --- a/plugins/Vestige/Vestige.cpp +++ b/plugins/Vestige/Vestige.cpp @@ -1052,8 +1052,8 @@ void ManageVestigeInstrumentView::syncPlugin( void ) std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); - m_vi->knobFModel[ i ]->setAutomatedValue( f_value ); - m_vi->knobFModel[ i ]->setInitValue( f_value ); + m_vi->knobFModel[i]->setValue(f_value, true); + m_vi->knobFModel[i]->setInitValue(f_value); } } syncParameterText(); diff --git a/plugins/VstEffect/VstEffectControls.cpp b/plugins/VstEffect/VstEffectControls.cpp index 277a285c5..de4516014 100644 --- a/plugins/VstEffect/VstEffectControls.cpp +++ b/plugins/VstEffect/VstEffectControls.cpp @@ -455,8 +455,8 @@ void ManageVSTEffectView::syncPlugin() std::snprintf(paramStr.data(), paramStr.size(), "param%d", i); s_dumpValues = dump[paramStr.data()].split(":"); float f_value = LocaleHelper::toFloat(s_dumpValues.at(2)); - m_vi2->knobFModel[ i ]->setAutomatedValue( f_value ); - m_vi2->knobFModel[ i ]->setInitValue( f_value ); + m_vi2->knobFModel[i]->setValue(f_value, true); + m_vi2->knobFModel[i]->setInitValue(f_value); } } syncParameterText(); diff --git a/src/core/AutomatableModel.cpp b/src/core/AutomatableModel.cpp index 4006c1d85..84e5b63f2 100644 --- a/src/core/AutomatableModel.cpp +++ b/src/core/AutomatableModel.cpp @@ -53,8 +53,9 @@ AutomatableModel::AutomatableModel( m_range( max - min ), m_centerValue( m_minValue ), m_valueChanged( false ), - m_setValueDepth( 0 ), + m_oldValue(val), m_hasStrictStepSize( false ), + m_nextLink(this), m_controllerConnection( nullptr ), m_valueBuffer( static_cast( Engine::audioEngine()->framesPerPeriod() ) ), m_lastUpdatedPeriod( -1 ), @@ -71,13 +72,9 @@ AutomatableModel::AutomatableModel( AutomatableModel::~AutomatableModel() { - while( m_linkedModels.empty() == false ) - { - m_linkedModels.back()->unlinkModel(this); - m_linkedModels.erase( m_linkedModels.end() - 1 ); - } + unlink(); - if( m_controllerConnection ) + if (m_controllerConnection) { delete m_controllerConnection; } @@ -294,41 +291,42 @@ void AutomatableModel::loadSettings( const QDomElement& element, const QString& -void AutomatableModel::setValue( const float value ) +void AutomatableModel::setValue(const float value, const bool isAutomated) +{ + if (fittedValue(value) == m_value) + { + // TODO check why we need this signal, is there a better solution? + if (!isAutomated) { emit dataUnchanged(); } + return; + } + + if (!isAutomated) { addJournalCheckPoint(); } + + // set value for this and the other linked models + setValueInternal(value); + for (auto model = m_nextLink; model != this; model = model->m_nextLink) + { + model->setValueInternal(value); + } +} + + + + +void AutomatableModel::setValueInternal(const float value) { m_oldValue = m_value; - ++m_setValueDepth; - const float old_val = m_value; + m_value = fittedValue(value); - m_value = fittedValue( value ); - if( old_val != m_value ) + if (m_oldValue != m_value) { - // add changes to history so user can undo it - addJournalCheckPoint(); - - // notify linked models - for (const auto& linkedModel : m_linkedModels) - { - if (linkedModel->m_setValueDepth < 1 && linkedModel->fittedValue(value) != linkedModel->m_value) - { - bool journalling = linkedModel->testAndSetJournalling(isJournalling()); - linkedModel->setValue(value); - linkedModel->setJournalling(journalling); - } - } m_valueChanged = true; emit dataChanged(); } - else - { - emit dataUnchanged(); - } - --m_setValueDepth; } - template T AutomatableModel::logToLinearScale( T value ) const { return castValue( lmms::logToLinearScale( minValue(), maxValue(), static_cast( value ) ) ); @@ -362,34 +360,6 @@ void AutomatableModel::roundAt( T& value, const T& where ) const -void AutomatableModel::setAutomatedValue( const float value ) -{ - setUseControllerValue(false); - - m_oldValue = m_value; - ++m_setValueDepth; - const float oldValue = m_value; - - const float scaled_value = scaledValue( value ); - - m_value = fittedValue( scaled_value ); - - if( oldValue != m_value ) - { - // notify linked models - for (const auto& linkedModel : m_linkedModels) - { - if (!(linkedModel->controllerConnection()) && linkedModel->m_setValueDepth < 1 && - linkedModel->fittedValue(m_value) != linkedModel->m_value) - { - linkedModel->setAutomatedValue(value); - } - } - m_valueChanged = true; - emit dataChanged(); - } - --m_setValueDepth; -} @@ -459,79 +429,67 @@ float AutomatableModel::fittedValue( float value ) const - -void AutomatableModel::linkModel( AutomatableModel* model ) +AutomatableModel* AutomatableModel::getLastLinkedModel() const { - auto containsModel = std::find(m_linkedModels.begin(), m_linkedModels.end(), model) != m_linkedModels.end(); - if (!containsModel && model != this) + for (auto model = m_nextLink; ; model = model->m_nextLink) { - m_linkedModels.push_back( model ); - - if( !model->hasLinkedModels() ) - { - QObject::connect( this, SIGNAL(dataChanged()), - model, SIGNAL(dataChanged()), Qt::DirectConnection ); - } + // The last model in the circular reference links back to this + if (model->m_nextLink == this) { return model; } } } -void AutomatableModel::unlinkModel( AutomatableModel* model ) +bool AutomatableModel::isLinkedToModel(AutomatableModel* model) const { - auto it = std::find(m_linkedModels.begin(), m_linkedModels.end(), model); - if( it != m_linkedModels.end() ) + if (model == this) { return true; } + for (auto next = m_nextLink; next != this; next = next->m_nextLink) { - m_linkedModels.erase( it ); + if (next == model) { return true; } } + return false; +} + +size_t AutomatableModel::countLinks() const +{ + size_t output = 0; + for (auto model = m_nextLink; model != this; model = model->m_nextLink) + { + output++; + } + return output; } - - -void AutomatableModel::linkModels( AutomatableModel* model1, AutomatableModel* model2 ) +void AutomatableModel::linkToModel(AutomatableModel* other) { - auto model1ContainsModel2 = std::find(model1->m_linkedModels.begin(), model1->m_linkedModels.end(), model2) != model1->m_linkedModels.end(); - if (!model1ContainsModel2 && model1 != model2) + if (isLinkedToModel(other)) { return; } + + if (valueBuffer() && other->valueBuffer()) { - // copy data - model1->m_value = model2->m_value; - if (model1->valueBuffer() && model2->valueBuffer()) - { - std::copy_n(model2->valueBuffer()->data(), - model1->valueBuffer()->length(), - model1->valueBuffer()->data()); - } - // send dataChanged() before linking (because linking will - // connect the two dataChanged() signals) - emit model1->dataChanged(); - // finally: link the models - model1->linkModel( model2 ); - model2->linkModel( model1 ); + std::copy_n(other->valueBuffer()->data(), + valueBuffer()->length(), + valueBuffer()->data()); } + // copy data from other to this + setValue(other->m_value); + // link the models + AutomatableModel* thisEnd = getLastLinkedModel(); + AutomatableModel* otherEnd = other->getLastLinkedModel(); + thisEnd->m_nextLink = other; + otherEnd->m_nextLink = this; } -void AutomatableModel::unlinkModels( AutomatableModel* model1, AutomatableModel* model2 ) +void AutomatableModel::unlink() { - model1->unlinkModel( model2 ); - model2->unlinkModel( model1 ); -} - - - - -void AutomatableModel::unlinkAllModels() -{ - for( AutomatableModel* model : m_linkedModels ) - { - unlinkModels( this, model ); - } + getLastLinkedModel()->m_nextLink = m_nextLink; + m_nextLink = this; } @@ -555,11 +513,11 @@ void AutomatableModel::setControllerConnection( ControllerConnection* c ) float AutomatableModel::controllerValue( int frameOffset ) const { - if( m_controllerConnection ) + assert(m_controllerConnection != nullptr); + + float v = 0; + switch (m_scaleType) { - float v = 0; - switch(m_scaleType) - { case ScaleType::Linear: v = minValue() + ( range() * controllerConnection()->currentValue( frameOffset ) ); break; @@ -571,24 +529,14 @@ float AutomatableModel::controllerValue( int frameOffset ) const qFatal("AutomatableModel::controllerValue(int)" "lacks implementation for a scale type"); break; - } - if (approximatelyEqual(m_step, 1) && m_hasStrictStepSize) - { - return std::round(v); - } - return v; } - - AutomatableModel* lm = m_linkedModels.front(); - if (lm->controllerConnection() && lm->useControllerValue()) + if (approximatelyEqual(m_step, 1) && m_hasStrictStepSize) { - return fittedValue( lm->controllerValue( frameOffset ) ); + return std::round(v); } - - return fittedValue( lm->m_value ); + return v; } - ValueBuffer * AutomatableModel::valueBuffer() { QMutexLocker m( &m_valueBufferMutex ); @@ -602,6 +550,11 @@ ValueBuffer * AutomatableModel::valueBuffer() float val = m_value; // make sure our m_value doesn't change midway + // TODO + // Let the Controller set the value of connected Models, + // instead of each Model checking the Controller value every time. + + // Get value buffer from our controller if (m_controllerConnection && m_useControllerValue && m_controllerConnection->getController()->isSampleExact()) { auto vb = m_controllerConnection->valueBuffer(); @@ -634,32 +587,36 @@ ValueBuffer * AutomatableModel::valueBuffer() } } - if (!m_controllerConnection) + // Get value buffer from one of the linked models' controller + if (m_useControllerValue) { - AutomatableModel* lm = nullptr; - if (hasLinkedModels()) + for (auto next = m_nextLink; next != this; next = next->m_nextLink) { - lm = m_linkedModels.front(); - } - if (lm && lm->controllerConnection() && lm->useControllerValue() && - lm->controllerConnection()->getController()->isSampleExact()) - { - auto vb = lm->valueBuffer(); - float * values = vb->values(); - float * nvalues = m_valueBuffer.values(); - for (int i = 0; i < vb->length(); i++) - { - nvalues[i] = fittedValue(values[i]); - } - m_lastUpdatedPeriod = s_periodCounter; - m_hasSampleExactData = true; - return &m_valueBuffer; + if (next->controllerConnection() && next->useControllerValue() && + next->controllerConnection()->getController()->isSampleExact()) + { + auto vb = next->valueBuffer(); + float* values = vb->values(); + float* nvalues = m_valueBuffer.values(); + for (int i = 0; i < vb->length(); i++) + { + nvalues[i] = fittedValue(values[i]); + } + m_lastUpdatedPeriod = s_periodCounter; + m_hasSampleExactData = true; + return &m_valueBuffer; + } } } - if( m_oldValue != val ) + // Note: if there are linked models but no controller, `setValue()` will have + // updated `m_value` and `m_oldValue` across all linked models, so the `valueBuffer()` + // will be the same (even if it is calculated separatly for each model). + + // Populate value buffer by interpolatating between the old and new value + if (m_oldValue != val) { - m_valueBuffer.interpolate( m_oldValue, val ); + m_valueBuffer.interpolate(m_oldValue, val); m_oldValue = val; m_lastUpdatedPeriod = s_periodCounter; m_hasSampleExactData = true; diff --git a/src/core/LadspaControl.cpp b/src/core/LadspaControl.cpp index 3282a0c7a..4227f7c4f 100644 --- a/src/core/LadspaControl.cpp +++ b/src/core/LadspaControl.cpp @@ -291,16 +291,15 @@ void LadspaControl::linkControls( LadspaControl * _control ) switch( m_port->data_type ) { case BufferDataType::Toggled: - BoolModel::linkModels( &m_toggledModel, _control->toggledModel() ); + _control->toggledModel()->linkToModel(&m_toggledModel); break; case BufferDataType::Integer: case BufferDataType::Enum: case BufferDataType::Floating: - FloatModel::linkModels( &m_knobModel, _control->knobModel() ); + _control->knobModel()->linkToModel(&m_knobModel); break; case BufferDataType::Time: - TempoSyncKnobModel::linkModels( &m_tempoSyncKnobModel, - _control->tempoSyncKnobModel() ); + _control->tempoSyncKnobModel()->linkToModel(&m_tempoSyncKnobModel); break; default: break; @@ -342,16 +341,15 @@ void LadspaControl::unlinkControls( LadspaControl * _control ) switch( m_port->data_type ) { case BufferDataType::Toggled: - BoolModel::unlinkModels( &m_toggledModel, _control->toggledModel() ); + _control->toggledModel()->unlink(); break; case BufferDataType::Integer: case BufferDataType::Enum: case BufferDataType::Floating: - FloatModel::unlinkModels( &m_knobModel, _control->knobModel() ); + _control->knobModel()->unlink(); break; case BufferDataType::Time: - TempoSyncKnobModel::unlinkModels( &m_tempoSyncKnobModel, - _control->tempoSyncKnobModel() ); + _control->tempoSyncKnobModel()->unlink(); break; default: break; diff --git a/src/core/LinkedModelGroups.cpp b/src/core/LinkedModelGroups.cpp index c52bce433..3778d4d80 100644 --- a/src/core/LinkedModelGroups.cpp +++ b/src/core/LinkedModelGroups.cpp @@ -46,7 +46,7 @@ void LinkedModelGroup::linkControls(LinkedModelGroup *other) { auto itr2 = other->m_models.find(id); Q_ASSERT(itr2 != other->m_models.end()); - AutomatableModel::linkModels(inf.m_model, itr2->second.m_model); + inf.m_model->linkToModel(itr2->second.m_model); }); } diff --git a/src/core/Song.cpp b/src/core/Song.cpp index f9cf716c7..0b42c219b 100644 --- a/src/core/Song.cpp +++ b/src/core/Song.cpp @@ -412,7 +412,7 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp for (auto it = m_oldAutomatedValues.begin(); it != m_oldAutomatedValues.end(); it++) { AutomatableModel * am = it.key(); - if (am->controllerConnection() && !values.contains(am)) + if (!values.contains(am)) { am->setUseControllerValue(true); } @@ -422,13 +422,18 @@ void Song::processAutomations(const TrackList &tracklist, TimePos timeStart, fpp // Apply values for (auto it = values.begin(); it != values.end(); it++) { - if (! recordedModels.contains(it.key())) + AutomatableModel* model = it.key(); + bool isRecording = recordedModels.contains(model); + model->setUseControllerValue(isRecording); + + if (!isRecording) { - it.key()->setAutomatedValue(it.value()); - } - else if (!it.key()->useControllerValue()) - { - it.key()->setUseControllerValue(true); + /* TODO + * Remove scaleValue() from here when automation editor's + * Y axis can be set to logarithmic, and automation clips store + * the actual values, and not the invertedScaledValue. + */ + model->setValue(model->scaledValue(it.value()), true); } } } diff --git a/src/gui/AutomatableModelView.cpp b/src/gui/AutomatableModelView.cpp index c52f9077c..c6b46c223 100644 --- a/src/gui/AutomatableModelView.cpp +++ b/src/gui/AutomatableModelView.cpp @@ -96,11 +96,11 @@ void AutomatableModelView::addDefaultActions( QMenu* menu ) menu->addSeparator(); - if( model->hasLinkedModels() ) + if (model->isLinked()) { - menu->addAction( embed::getIconPixmap( "edit-delete" ), - AutomatableModel::tr( "Remove all linked controls" ), - amvSlots, SLOT(unlinkAllModels())); + menu->addAction(embed::getIconPixmap("edit_unlink"), + AutomatableModel::tr("Remove all linked controls"), + model, SLOT(unlink())); menu->addSeparator(); } @@ -276,11 +276,6 @@ void AutomatableModelViewSlots::removeSongGlobalAutomation() } -void AutomatableModelViewSlots::unlinkAllModels() -{ - m_amv->modelUntyped()->unlinkAllModels(); -} - void AutomatableModelViewSlots::copyToClipboard() { // For copyString() and MimeType enum class diff --git a/src/gui/widgets/FloatModelEditorBase.cpp b/src/gui/widgets/FloatModelEditorBase.cpp index 357505337..0f3a513c0 100644 --- a/src/gui/widgets/FloatModelEditorBase.cpp +++ b/src/gui/widgets/FloatModelEditorBase.cpp @@ -147,8 +147,7 @@ void FloatModelEditorBase::dropEvent(QDropEvent * de) auto mod = dynamic_cast(Engine::projectJournal()->journallingObject(val.toInt())); if (mod != nullptr) { - AutomatableModel::linkModels(model(), mod); - mod->setValue(model()->value()); + model()->linkToModel(mod); } } } @@ -423,12 +422,16 @@ void FloatModelEditorBase::enterValue() void FloatModelEditorBase::friendlyUpdate() { - if (model() && (model()->controllerConnection() == nullptr || - model()->controllerConnection()->getController()->frequentUpdates() == false || - Controller::runningFrames() % (256*4) == 0)) - { - update(); - } + if (model() == nullptr) { return; } + + // If the controller changes constantly, only repaint every 1024th frame + if (model()->useControllerValue() + && model()->controllerConnection() + && model()->controllerConnection()->getController()->frequentUpdates() + && Controller::runningFrames() % (256 * 4) != 0) + { return; } + + update(); } diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp index 9bc5fd6ac..6fa86d20c 100644 --- a/tests/src/core/AutomatableModelTest.cpp +++ b/tests/src/core/AutomatableModelTest.cpp @@ -78,7 +78,7 @@ private slots: // tests { using namespace lmms; - BoolModel m1(false), m2(false); + BoolModel m1(true), m2(false); QObject::connect(&m1, SIGNAL(dataChanged()), this, SLOT(onM1Changed())); @@ -86,17 +86,19 @@ private slots: // tests this, SLOT(onM2Changed())); resetChanged(); - AutomatableModel::linkModels(&m1, &m1); + m1.linkToModel(&m1); QVERIFY(!m1Changed); // cannot link to itself QVERIFY(!m2Changed); + QVERIFY(m1.countLinks() == 0); resetChanged(); - AutomatableModel::linkModels(&m1, &m2); - QVERIFY(m1Changed); // since m1 takes the value of m2 + m1.linkToModel(&m2); + QVERIFY(m1.value() == m2.value()); // since m1 takes the value of m2 QVERIFY(!m2Changed); // the second model is the source + QVERIFY(m1.countLinks() == 1); resetChanged(); - AutomatableModel::linkModels(&m1, &m2); + m1.linkToModel(&m2); QVERIFY(!m1Changed); // it's already linked QVERIFY(!m2Changed); @@ -104,15 +106,15 @@ private slots: // tests BoolModel m3(false); m1.setValue(1.f); m2.setValue(1.f); - AutomatableModel::linkModels(&m1, &m2); + m1.linkToModel(&m2); QVERIFY(m1.value()); QVERIFY(m2.value()); QVERIFY(!m3.value()); - AutomatableModel::linkModels(&m2, &m3); // drag m3, drop on m2 + m2.linkToModel(&m3); // drag m3, drop on m2 // m2 should take m3's (0) value - // due to a bug(?), this does not happen - QVERIFY(m2.value()); + QVERIFY(m2.value() == m3.value()); QVERIFY(!m3.value()); + QVERIFY(m1.countLinks() == 2); } };