diff --git a/src/common/visitor.hpp b/src/common/visitor.hpp index c9a375f92..7dde5e5c6 100644 --- a/src/common/visitor.hpp +++ b/src/common/visitor.hpp @@ -82,7 +82,7 @@ namespace cinelerra template < typename RET = void, class TOOL = Tool, - template class ERR = UseDefault + template class ERR = UseDefault > class Visitable { @@ -108,7 +108,7 @@ namespace cinelerra return concreteTool->treat (target); else - return ERR::onUnknown (target,tool); + return ERR::onUnknown (target,tool); } }; diff --git a/src/common/visitorpolicies.hpp b/src/common/visitorpolicies.hpp index c4baa1646..2caadf5b2 100644 --- a/src/common/visitorpolicies.hpp +++ b/src/common/visitorpolicies.hpp @@ -48,10 +48,9 @@ namespace cinelerra * of encountering an unknown Visitor (typically caused by * adding a new class to the visitable hierarchy) */ - template + template struct UseDefault { - template static RET onUnknown (TAR&, TOOL&) { return RET(); @@ -61,10 +60,9 @@ namespace cinelerra /** * Policy to throw when encountering an unknown visiting tool */ - template + template struct ThrowException { - template static RET onUnknown (TAR&, TOOL&) { throw cinelerra::error::Config("unable to decide what tool operation to call"); @@ -74,14 +72,15 @@ namespace cinelerra /** * Policy invoking an catch-all function for processing * an unknown tool / target pair + * @note using this policy effectively enforces + * implementing a catch-all function \c treat(TAR&) */ - template + template struct InvokeCatchAllFunction { - template static RET onUnknown (TAR& target,TOOL& tool) { - target.apply (static_cast (tool)); + tool.treat (target); } }; diff --git a/src/proc/asset.hpp b/src/proc/asset.hpp index 923fec6a8..dabb73039 100644 --- a/src/proc/asset.hpp +++ b/src/proc/asset.hpp @@ -309,7 +309,7 @@ namespace asset /** convienient for debugging */ - inline string str (PcAsset& a) + inline string str (const PcAsset& a) { if (a) return string (*a.get()); diff --git a/src/proc/mobject/mobject.hpp b/src/proc/mobject/mobject.hpp index 49110b9ac..08fda383d 100644 --- a/src/proc/mobject/mobject.hpp +++ b/src/proc/mobject/mobject.hpp @@ -29,6 +29,7 @@ #include "cinelerra.h" #include "proc/mobject/buildable.hpp" +#include "proc/mobject/builder/buildertool.hpp" #include "proc/mobject/placement.hpp" #include "proc/asset.hpp" // TODO finally not needed? diff --git a/src/proc/mobject/session/abstractmo.hpp b/src/proc/mobject/session/abstractmo.hpp index ee73228a4..bd4fd5785 100644 --- a/src/proc/mobject/session/abstractmo.hpp +++ b/src/proc/mobject/session/abstractmo.hpp @@ -47,6 +47,7 @@ namespace mobject virtual Time& getLength() { return length; } + DEFINE_PROCESSABLE_BY (builder::BuilderTool); }; diff --git a/src/proc/mobject/session/clip.hpp b/src/proc/mobject/session/clip.hpp index fd6748b0f..fd87b1c39 100644 --- a/src/proc/mobject/session/clip.hpp +++ b/src/proc/mobject/session/clip.hpp @@ -81,6 +81,8 @@ namespace mobject */ PClipAsset findClipAsset () const; + DEFINE_PROCESSABLE_BY (builder::BuilderTool); + }; typedef Placement PClipMO; diff --git a/tests/50components.tests b/tests/50components.tests index 6c6c5f20c..0e71172d0 100644 --- a/tests/50components.tests +++ b/tests/50components.tests @@ -183,8 +183,17 @@ END TEST "VisitingTool_test" VisitingTool_test <, - public Applicable + : public Tool { + protected: void talk_to (string guy) { cout << format ("Hello %s, nice to meet you...\n") % guy; } + }; + + class Babbler + : public VerboseVisitor, + public Applicable, + public Applicable + { public: void treat (Boss&) { talk_to("Boss"); } void treat (BigBoss&) { talk_to("big Boss"); } }; + + // the classes above comprise the standard use case, + // what follows are rather exotic corner cases + + class Blatherer + : public VerboseVisitor, + public Applicable + { + public: + void treat (BigBoss&) { talk_to("big Boss"); } + void treat (HomoSapiens&) { talk_to("we-do-everything-for-YOU"); } ///< catch-all function + }; + + + typedef Visitable Vista2; + + class Chief : public Vista2 ///< abstract intermeidary node + { + }; + + class Leader : public Chief, + public Boss ///< can act as HomoSapiens or as Chief + { + public: + using HomoSapiens::apply; + virtual void apply (Blatherer& tool) { return Vista2::dispatchOp (*this, tool); } + }; + + class Visionary : public Leader + { + }; + + + @@ -97,34 +133,83 @@ namespace cinelerra virtual void run(Arg arg) { known_visitor_known_class(); - TODO ("implement the more complicated visitor test cases"); - //visitor_not_visiting_some_class(); - //visitor_treating_new_subclass(); + visitor_not_visiting_some_class(); + visitor_treating_new_subclass(); } void known_visitor_known_class() { - HomoSapiens x1; - Boss x2; - BigBoss x3; + Boss x1; + BigBoss x2; - HomoSapiens& xx2 (x2); - HomoSapiens& xx3 (x3); + // masquerade as HomoSapiens... + HomoSapiens& homo1 (x1); + HomoSapiens& homo2 (x2); - VerboseVisitor wizzy; - x1.apply (wizzy); - xx2.apply (wizzy); - xx3.apply (wizzy); + cout << "=== Babbler meets Boss and BigBoss ===\n"; + Babbler bab; + homo1.apply (bab); + homo2.apply (bab); } void visitor_not_visiting_some_class() { - UNIMPLEMENTED ("testing the generic visitor pattern"); + HomoSapiens x1; + Leader x2; + + HomoSapiens& homo1 (x1); + HomoSapiens& homo2 (x2); + + cout << "=== Babbler meets HomoSapiens and Leader ===\n"; + Babbler bab; + homo1.apply (bab); // doesn't visit HomoSapiens + homo2.apply (bab); // treats Leader as Boss } void visitor_treating_new_subclass() { - UNIMPLEMENTED ("testing the generic visitor pattern"); + Leader x1; + Visionary x2; + HomoSapiens x3; + + HomoSapiens& homo1 (x1); + HomoSapiens& homo2 (x2); + HomoSapiens& homo3 (x3); + Chief& chief1 (x1); + Chief& chief2 (x2); + Leader& lead1 (x1); + Leader& lead2 (x2); + + Blatherer bla; + VerboseVisitor vista; + Tool& tool1 (vista); + Tool& tool2 (bla); + cout << "=== Blatherer meets Leader, Visionary and HomoSapiens masqueraded as HomoSapiens ===\n"; + homo1.apply (bla); // nothing happens, because Blatherer doesn't declare to do anything as Tool + homo2.apply (bla); + homo3.apply (bla); + cout << "=== Blatherer meets Leader and Visionary masqueraded as Chief ===\n"; + chief1.apply (bla); // but now, acting in the Chief hierarchy, the catch-all is called + chief2.apply (bla); + cout << "=== VerboseVistr masqueraded as Tool meets Leader and Visionary masqueraded as HomoSapiens ===\n"; + homo1.apply (tool1); // because acting in the HomoSapiens hierarch, no visiting happens and no catch-all + homo2.apply (tool1); + cout << "=== Blatherer masqueraded as Tool meets Leader and Visionary masqueraded as Leader ===\n"; + lead1.apply (tool2); // nothing happens, because Leader here is treated by his HomoSapiens base + lead2.apply (tool2); + + // note: the following doesn't compile (an this is a feature, not a bug): + + // "chief1.apply (tool2)" : because the "Chief"-hierarchy enforces the catch-all function + // and the compiler doesn't know Blatherer actually implements this + // catch-all-function, because of the masqueradeing as Tool. Note + // further: the catch-all function can have a more general type + // (in this case HomoSapiens instead of Chief) + + // "Chief chief" : is abstract, because the Visitable-Template enforces implementing + // the "apply(TOOL&)" function, either directly or via the + // DEFINE_PROCESSABLE_BY macro + } }; diff --git a/tests/components/proc/mobject/builder/buildertooltest.cpp b/tests/components/proc/mobject/builder/buildertooltest.cpp index 6e277441a..cbe68007e 100644 --- a/tests/components/proc/mobject/builder/buildertooltest.cpp +++ b/tests/components/proc/mobject/builder/buildertooltest.cpp @@ -23,13 +23,11 @@ #include "common/test/run.hpp" #include "proc/mobject/builder/buildertool.hpp" -//#include "common/factory.hpp" -//#include "common/util.hpp" +#include "proc/asset/category.hpp" +#include "proc/asset/media.hpp" +#include "proc/mobject/session/clip.hpp" -//#include #include - -//using boost::format; using std::string; using std::cout; @@ -41,6 +39,11 @@ namespace mobject namespace test { + using session::Clip; + using session::AbstractMO; + using cinelerra::visitor::Applicable; + + @@ -49,14 +52,44 @@ namespace mobject * MObjects in the builder. Because the generic visitor * implementation is already covered by * \link VisitingTool_test, it is sufficient to test - * the specialisation to the builder - * @todo work out what this means haha.... + * the specialisation to the builder. + * \par + * Besides using existing MObject types (at the moment session::Clip), + * we create inline a yet-unknown new MObject subclass. When passing + * such to any BuilderTool subclass, the compiler enforces the definition + * of a catch-all function, which is called, when there is no other + * applicable \c treat(MO&) function. Note further, whithin the specific + * treat-functions we get direct references, whithout interfering with + * Placements and memory management. (Is this a good idea? it allows + * client code to bypass the Placement (Handle). At least, I think + * it is not really a problem...) */ class BuilderTool_test : public Test { virtual void run(Arg arg) { - UNIMPLEMENTED ("testing the visitor pattern for the builder"); + class DummyMO : public AbstractMO + { + public: + DummyMO() { }; + virtual bool isValid() const { return true;} + }; + + class TestTool : public BuilderTool, + public Applicable + { + void treat (Clip& c) { cout << "media is: "<< str(c.getMedia()) <<"\n"; } + void treat (Buildable&){ cout << "catch-all-function called.\n"; } + }; + + TestTool t1; + BuilderTool& tool (t1); + + DummyMO dumm; + PMO clip = asset::Media::create("test-1", asset::VIDEO)->createClip(); + + clip->apply (tool); + dumm.apply (tool); } };