From 5101aa021440c9e6c183fd197af09154df5d619a Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sat, 16 Mar 2019 20:19:27 +0100 Subject: [PATCH 1/6] Make Model class visitable --- include/AutomatableModel.h | 45 ++++++++++++++++++++++++++++++++ include/ComboBoxModel.h | 1 + include/ModelVisitor.h | 53 ++++++++++++++++++++++++++++++++++++++ src/core/CMakeLists.txt | 1 + src/core/ModelVisitor.cpp | 28 ++++++++++++++++++++ 5 files changed, 128 insertions(+) create mode 100644 include/ModelVisitor.h create mode 100644 src/core/ModelVisitor.cpp diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 78f4882b423..f5215676e3b 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -33,6 +33,7 @@ #include "MidiTime.h" #include "ValueBuffer.h" #include "MemoryManager.h" +#include "ModelVisitor.h" // simple way to map a property of a view to a model #define mapPropertyFromModelPtr(type,getfunc,setfunc,modelname) \ @@ -68,6 +69,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject Q_OBJECT MM_OPERATORS public: + typedef QVector AutoModelVector; enum ScaleType @@ -80,6 +82,32 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject virtual ~AutomatableModel(); + // Implement those by using the MODEL_IS_VISITABLE macro + virtual void accept(ModelVisitor& v) = 0; + virtual void accept(ConstModelVisitor& v) const = 0; + // use this to make subclasses visitable +#define MODEL_IS_VISITABLE \ + void accept(ModelVisitor& v) override { v.visit(*this); } \ + void accept(ConstModelVisitor& v) const override { v.visit(*this); } + +public: + //! Return this class casted to Target, or nullptr if impossible + template + Target* dcast(bool doThrow = false) + { + DCastVisitor vis; accept(vis); + if(doThrow && !vis.result) Q_ASSERT(false); + return vis.result; + } + + //! Return this class casted to const Target, or nullptr if impossible + template + const Target* dcast(bool doThrow = false) const + { + ConstDCastVisitor vis; accept(vis); + if(doThrow && !vis.result) Q_ASSERT(false); + return vis.result; + } bool isAutomated() const; bool isAutomatedOrControlled() const @@ -283,6 +311,20 @@ public slots: private: + template + struct DCastVisitor : public ModelVisitor + { + Target* result = nullptr; + void visit(Target& tar) { result = &tar; } + }; + + template + struct ConstDCastVisitor : public ConstModelVisitor + { + const Target* result = nullptr; + void visit(const Target& tar) { result = &tar; } + }; + static bool mustQuoteName(const QString &name); virtual void saveSettings( QDomDocument& doc, QDomElement& element ) @@ -382,6 +424,7 @@ template class LMMS_EXPORT TypedAutomatableModel : public Automatab class LMMS_EXPORT FloatModel : public TypedAutomatableModel { Q_OBJECT + MODEL_IS_VISITABLE public: FloatModel( float val = 0, float min = 0, float max = 0, float step = 0, Model * parent = NULL, @@ -399,6 +442,7 @@ class LMMS_EXPORT FloatModel : public TypedAutomatableModel class LMMS_EXPORT IntModel : public TypedAutomatableModel { Q_OBJECT + MODEL_IS_VISITABLE public: IntModel( int val = 0, int min = 0, int max = 0, Model* parent = NULL, @@ -414,6 +458,7 @@ class LMMS_EXPORT IntModel : public TypedAutomatableModel class LMMS_EXPORT BoolModel : public TypedAutomatableModel { Q_OBJECT + MODEL_IS_VISITABLE public: BoolModel( const bool val = false, Model* parent = NULL, diff --git a/include/ComboBoxModel.h b/include/ComboBoxModel.h index ad3603759ad..82c01e69e94 100644 --- a/include/ComboBoxModel.h +++ b/include/ComboBoxModel.h @@ -36,6 +36,7 @@ class LMMS_EXPORT ComboBoxModel : public IntModel { Q_OBJECT + MODEL_IS_VISITABLE public: ComboBoxModel( Model* parent = NULL, const QString& displayName = QString(), diff --git a/include/ModelVisitor.h b/include/ModelVisitor.h new file mode 100644 index 00000000000..59d1df0c62a --- /dev/null +++ b/include/ModelVisitor.h @@ -0,0 +1,53 @@ +/* + * ModelVisitor.h - visitors for automatable models + * + * Copyright (c) 2019-2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#ifndef MODELVISITOR_H +#define MODELVISITOR_H + +class BoolModel; +class IntModel; +class FloatModel; +class ComboBoxModel; + +class ModelVisitor +{ +public: + virtual void visit(BoolModel& ) {} + virtual void visit(IntModel& ) {} + virtual void visit(FloatModel& ) {} + virtual void visit(ComboBoxModel& ) {} + virtual ~ModelVisitor(); +}; + +class ConstModelVisitor +{ +public: + virtual void visit(const BoolModel& ) {} + virtual void visit(const IntModel& ) {} + virtual void visit(const FloatModel& ) {} + virtual void visit(const ComboBoxModel& ) {} + virtual ~ConstModelVisitor(); +}; + +#endif // MODELVISITOR_H diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 85a00780b10..b573b93b0bd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -40,6 +40,7 @@ set(LMMS_SRCS core/MixerWorkerThread.cpp core/MixHelpers.cpp core/Model.cpp + core/ModelVisitor.cpp core/Note.cpp core/NotePlayHandle.cpp core/Oscillator.cpp diff --git a/src/core/ModelVisitor.cpp b/src/core/ModelVisitor.cpp new file mode 100644 index 00000000000..11a8fc1b11a --- /dev/null +++ b/src/core/ModelVisitor.cpp @@ -0,0 +1,28 @@ +/* + * ModelVisitor.cpp - visitors for automatable models + * + * Copyright (c) 2019-2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "ModelVisitor.h" + +ModelVisitor::~ModelVisitor() {} +ConstModelVisitor::~ConstModelVisitor() {} From d06f5088a152f3bb228050f8fe25673332ce66fd Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sat, 20 Apr 2019 13:09:49 +0200 Subject: [PATCH 2/6] Move macro definition out of class --- include/AutomatableModel.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index f5215676e3b..a92bd51913d 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -60,6 +60,11 @@ modelname.setValue( (float) val ); \ } +// use this to make subclasses visitable +#define MODEL_IS_VISITABLE \ + void accept(ModelVisitor& v) override { v.visit(*this); } \ + void accept(ConstModelVisitor& v) const override { v.visit(*this); } + class ControllerConnection; @@ -85,10 +90,6 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject // Implement those by using the MODEL_IS_VISITABLE macro virtual void accept(ModelVisitor& v) = 0; virtual void accept(ConstModelVisitor& v) const = 0; - // use this to make subclasses visitable -#define MODEL_IS_VISITABLE \ - void accept(ModelVisitor& v) override { v.visit(*this); } \ - void accept(ConstModelVisitor& v) const override { v.visit(*this); } public: //! Return this class casted to Target, or nullptr if impossible From 0fd5693e12cf2e5cae0967763854382e0ad376be Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sat, 27 Apr 2019 00:29:49 +0200 Subject: [PATCH 3/6] Improve dcast * document `dcast` * make `dcast` not only cast exact, but also upwards * add `dcast` test * rename `dcast` -> `dynamicCast` --- include/AutomatableModel.h | 17 ++++++-- include/ModelVisitor.h | 24 +++++++---- src/core/ModelVisitor.cpp | 13 ++++++ tests/CMakeLists.txt | 1 + tests/src/core/AutomatableModelTest.cpp | 53 +++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 tests/src/core/AutomatableModelTest.cpp diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index a92bd51913d..93550e33f65 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -92,18 +92,25 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject virtual void accept(ConstModelVisitor& v) const = 0; public: - //! Return this class casted to Target, or nullptr if impossible + /** + @brief Return this class casted to Target + @test AutomatableModelTest.cpp + @param doThrow throw an assertion if the cast fails, instead of + returning a nullptr + @return the casted class if Target is the exact or a base class of + *this, nullptr otherwise + */ template - Target* dcast(bool doThrow = false) + Target* dynamicCast(bool doThrow = false) { DCastVisitor vis; accept(vis); if(doThrow && !vis.result) Q_ASSERT(false); return vis.result; } - //! Return this class casted to const Target, or nullptr if impossible + //! const overload, see overloaded function template - const Target* dcast(bool doThrow = false) const + const Target* dynamicCast(bool doThrow = false) const { ConstDCastVisitor vis; accept(vis); if(doThrow && !vis.result) Q_ASSERT(false); @@ -312,6 +319,7 @@ public slots: private: + // dynamicCast implementation template struct DCastVisitor : public ModelVisitor { @@ -319,6 +327,7 @@ public slots: void visit(Target& tar) { result = &tar; } }; + // dynamicCast implementation template struct ConstDCastVisitor : public ConstModelVisitor { diff --git a/include/ModelVisitor.h b/include/ModelVisitor.h index 59d1df0c62a..6411d070236 100644 --- a/include/ModelVisitor.h +++ b/include/ModelVisitor.h @@ -25,6 +25,7 @@ #ifndef MODELVISITOR_H #define MODELVISITOR_H +class AutomatableModel; class BoolModel; class IntModel; class FloatModel; @@ -32,21 +33,28 @@ class ComboBoxModel; class ModelVisitor { + template + void up(ModelType& m) { visit(static_cast(m)); } public: - virtual void visit(BoolModel& ) {} - virtual void visit(IntModel& ) {} - virtual void visit(FloatModel& ) {} - virtual void visit(ComboBoxModel& ) {} + virtual void visit(AutomatableModel& ) {} + virtual void visit(BoolModel& m); + virtual void visit(IntModel& ); + virtual void visit(FloatModel& ); + virtual void visit(ComboBoxModel& ); virtual ~ModelVisitor(); }; class ConstModelVisitor { + template + void up(const ModelType& m) { + visit(static_cast(m)); } public: - virtual void visit(const BoolModel& ) {} - virtual void visit(const IntModel& ) {} - virtual void visit(const FloatModel& ) {} - virtual void visit(const ComboBoxModel& ) {} + virtual void visit(const AutomatableModel& ) {} + virtual void visit(const BoolModel& m); + virtual void visit(const IntModel& m); + virtual void visit(const FloatModel& m); + virtual void visit(const ComboBoxModel& m); virtual ~ConstModelVisitor(); }; diff --git a/src/core/ModelVisitor.cpp b/src/core/ModelVisitor.cpp index 11a8fc1b11a..48065c57eea 100644 --- a/src/core/ModelVisitor.cpp +++ b/src/core/ModelVisitor.cpp @@ -24,5 +24,18 @@ #include "ModelVisitor.h" +#include "AutomatableModel.h" +#include "ComboBoxModel.h" + +void ModelVisitor::visit(BoolModel &m) { up(m); } +void ModelVisitor::visit(IntModel &m) { up(m); } +void ModelVisitor::visit(FloatModel &m) { up(m); } +void ModelVisitor::visit(ComboBoxModel &m) { up(m); } + +void ConstModelVisitor::visit(const BoolModel &m) { up(m); } +void ConstModelVisitor::visit(const IntModel &m) { up(m); } +void ConstModelVisitor::visit(const FloatModel &m) { up(m); } +void ConstModelVisitor::visit(const ComboBoxModel &m) { up(m); } + ModelVisitor::~ModelVisitor() {} ConstModelVisitor::~ConstModelVisitor() {} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c39f8e56ed9..ddebe116c6e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -14,6 +14,7 @@ ADD_EXECUTABLE(tests QTestSuite $ + src/core/AutomatableModelTest.cpp src/core/ProjectVersionTest.cpp src/core/RelativePathsTest.cpp diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp new file mode 100644 index 00000000000..9bc19d7e863 --- /dev/null +++ b/tests/src/core/AutomatableModelTest.cpp @@ -0,0 +1,53 @@ +/* + * AutomatableModelTest.cpp + * + * Copyright (c) 2019-2019 Johannes Lorenz + * + * This file is part of LMMS - https://lmms.io + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program (see COPYING); if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + */ + +#include "QTestSuite.h" + +#include "AutomatableModel.h" +#include "ComboBoxModel.h" + +class AutomatableModelTest : QTestSuite +{ + Q_OBJECT + +private slots: + void CastTests() + { + ComboBoxModel comboModel; + AutomatableModel* amPtr = &comboModel; + QCOMPARE(nullptr, amPtr->dynamicCast()); + QVERIFY(nullptr != amPtr->dynamicCast()); + QVERIFY(nullptr != amPtr->dynamicCast()); + QVERIFY(nullptr != amPtr->dynamicCast()); + + IntModel intModel; + IntModel* imPtr = &intModel; + QCOMPARE(nullptr, imPtr->dynamicCast()); + QVERIFY(nullptr != imPtr->dynamicCast()); + QVERIFY(nullptr != imPtr->dynamicCast()); + QCOMPARE(nullptr, imPtr->dynamicCast()); + } +} AutomatableModelTests; + +#include "AutomatableModelTest.moc" From 8d005e7565b80f161a51bebb26cf62c700b39dd6 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sat, 27 Apr 2019 10:48:37 +0200 Subject: [PATCH 4/6] AutomatableModelTest: Improve tests Check whether returned pointers from the cast are equal to the original pointers, rather than just checking wether they are not `nullptr`. --- tests/src/core/AutomatableModelTest.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp index 9bc19d7e863..6717da12d50 100644 --- a/tests/src/core/AutomatableModelTest.cpp +++ b/tests/src/core/AutomatableModelTest.cpp @@ -32,21 +32,23 @@ class AutomatableModelTest : QTestSuite Q_OBJECT private slots: + //! Test that upcast and exact casts work, + //! but no downcast or any other casts void CastTests() { ComboBoxModel comboModel; AutomatableModel* amPtr = &comboModel; - QCOMPARE(nullptr, amPtr->dynamicCast()); - QVERIFY(nullptr != amPtr->dynamicCast()); - QVERIFY(nullptr != amPtr->dynamicCast()); - QVERIFY(nullptr != amPtr->dynamicCast()); + QCOMPARE(nullptr, amPtr->dynamicCast()); // not a parent class + QCOMPARE(&comboModel, amPtr->dynamicCast()); // parent class + QCOMPARE(&comboModel, amPtr->dynamicCast()); // parent class + QCOMPARE(&comboModel, amPtr->dynamicCast()); // same class IntModel intModel; IntModel* imPtr = &intModel; - QCOMPARE(nullptr, imPtr->dynamicCast()); - QVERIFY(nullptr != imPtr->dynamicCast()); - QVERIFY(nullptr != imPtr->dynamicCast()); - QCOMPARE(nullptr, imPtr->dynamicCast()); + QCOMPARE(nullptr, imPtr->dynamicCast()); // not a parent class + QCOMPARE(&intModel, imPtr->dynamicCast()); // parent class + QCOMPARE(&intModel, imPtr->dynamicCast()); // same class + QCOMPARE(nullptr, imPtr->dynamicCast()); // child class } } AutomatableModelTests; From 777da5e391a81ed561767708c2f86a6b7b36af09 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sat, 27 Apr 2019 11:11:48 +0200 Subject: [PATCH 5/6] Fix CI on windows --- tests/src/core/AutomatableModelTest.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/src/core/AutomatableModelTest.cpp b/tests/src/core/AutomatableModelTest.cpp index 6717da12d50..116f95e60a0 100644 --- a/tests/src/core/AutomatableModelTest.cpp +++ b/tests/src/core/AutomatableModelTest.cpp @@ -38,17 +38,17 @@ private slots: { ComboBoxModel comboModel; AutomatableModel* amPtr = &comboModel; - QCOMPARE(nullptr, amPtr->dynamicCast()); // not a parent class + QVERIFY(nullptr == amPtr->dynamicCast()); // not a parent class QCOMPARE(&comboModel, amPtr->dynamicCast()); // parent class QCOMPARE(&comboModel, amPtr->dynamicCast()); // parent class QCOMPARE(&comboModel, amPtr->dynamicCast()); // same class IntModel intModel; IntModel* imPtr = &intModel; - QCOMPARE(nullptr, imPtr->dynamicCast()); // not a parent class + QVERIFY(nullptr == imPtr->dynamicCast()); // not a parent class QCOMPARE(&intModel, imPtr->dynamicCast()); // parent class QCOMPARE(&intModel, imPtr->dynamicCast()); // same class - QCOMPARE(nullptr, imPtr->dynamicCast()); // child class + QVERIFY(nullptr == imPtr->dynamicCast()); // child class } } AutomatableModelTests; From 2c134d65fed33930801581e8c906ed1eb0048112 Mon Sep 17 00:00:00 2001 From: Johannes Lorenz Date: Sat, 27 Apr 2019 15:26:17 +0200 Subject: [PATCH 6/6] Code style + Extend for TempoSyncKnob --- include/AutomatableModel.h | 4 ++-- include/ModelVisitor.h | 9 ++++++--- include/TempoSyncKnobModel.h | 1 + src/core/ModelVisitor.cpp | 3 +++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/include/AutomatableModel.h b/include/AutomatableModel.h index 93550e33f65..3e0b6143da3 100644 --- a/include/AutomatableModel.h +++ b/include/AutomatableModel.h @@ -104,7 +104,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject Target* dynamicCast(bool doThrow = false) { DCastVisitor vis; accept(vis); - if(doThrow && !vis.result) Q_ASSERT(false); + if (doThrow && !vis.result) { Q_ASSERT(false); } return vis.result; } @@ -113,7 +113,7 @@ class LMMS_EXPORT AutomatableModel : public Model, public JournallingObject const Target* dynamicCast(bool doThrow = false) const { ConstDCastVisitor vis; accept(vis); - if(doThrow && !vis.result) Q_ASSERT(false); + if (doThrow && !vis.result) { Q_ASSERT(false); } return vis.result; } diff --git a/include/ModelVisitor.h b/include/ModelVisitor.h index 6411d070236..f9d156e30f4 100644 --- a/include/ModelVisitor.h +++ b/include/ModelVisitor.h @@ -30,6 +30,7 @@ class BoolModel; class IntModel; class FloatModel; class ComboBoxModel; +class TempoSyncKnobModel; class ModelVisitor { @@ -38,9 +39,10 @@ class ModelVisitor public: virtual void visit(AutomatableModel& ) {} virtual void visit(BoolModel& m); - virtual void visit(IntModel& ); - virtual void visit(FloatModel& ); - virtual void visit(ComboBoxModel& ); + virtual void visit(IntModel& m); + virtual void visit(FloatModel& m); + virtual void visit(ComboBoxModel& m); + virtual void visit(TempoSyncKnobModel& m); virtual ~ModelVisitor(); }; @@ -55,6 +57,7 @@ class ConstModelVisitor virtual void visit(const IntModel& m); virtual void visit(const FloatModel& m); virtual void visit(const ComboBoxModel& m); + virtual void visit(const TempoSyncKnobModel& m); virtual ~ConstModelVisitor(); }; diff --git a/include/TempoSyncKnobModel.h b/include/TempoSyncKnobModel.h index 9a8ad619c11..52fab30bbab 100644 --- a/include/TempoSyncKnobModel.h +++ b/include/TempoSyncKnobModel.h @@ -33,6 +33,7 @@ class QAction; class LMMS_EXPORT TempoSyncKnobModel : public FloatModel { Q_OBJECT + MODEL_IS_VISITABLE public: enum TempoSyncMode { diff --git a/src/core/ModelVisitor.cpp b/src/core/ModelVisitor.cpp index 48065c57eea..4036f56e0a3 100644 --- a/src/core/ModelVisitor.cpp +++ b/src/core/ModelVisitor.cpp @@ -26,16 +26,19 @@ #include "AutomatableModel.h" #include "ComboBoxModel.h" +#include "TempoSyncKnobModel.h" void ModelVisitor::visit(BoolModel &m) { up(m); } void ModelVisitor::visit(IntModel &m) { up(m); } void ModelVisitor::visit(FloatModel &m) { up(m); } void ModelVisitor::visit(ComboBoxModel &m) { up(m); } +void ModelVisitor::visit(TempoSyncKnobModel &m) { up(m); } void ConstModelVisitor::visit(const BoolModel &m) { up(m); } void ConstModelVisitor::visit(const IntModel &m) { up(m); } void ConstModelVisitor::visit(const FloatModel &m) { up(m); } void ConstModelVisitor::visit(const ComboBoxModel &m) { up(m); } +void ConstModelVisitor::visit(const TempoSyncKnobModel &m) { up(m); } ModelVisitor::~ModelVisitor() {} ConstModelVisitor::~ConstModelVisitor() {}