From 6a4dcc673a074292236192209ca67e54d8b0c6a7 Mon Sep 17 00:00:00 2001 From: itsmattkc Date: Tue, 17 Dec 2019 15:44:13 +1100 Subject: [PATCH] implemented external xml/shader loading for transitions as well as regular nodes Transitions are nodes but have a few parameters that are programmatic and always required. This lets people write external code for transitions as well. --- app/node/CMakeLists.txt | 2 + app/node/block/transition/CMakeLists.txt | 2 + .../block/transition/externaltransition.cpp | 64 ++++ .../block/transition/externaltransition.h | 32 ++ app/node/external.cpp | 248 +-------------- app/node/external.h | 24 +- app/node/metareader.cpp | 299 ++++++++++++++++++ app/node/metareader.h | 61 ++++ app/node/node.h | 4 +- 9 files changed, 477 insertions(+), 259 deletions(-) create mode 100644 app/node/block/transition/externaltransition.cpp create mode 100644 app/node/block/transition/externaltransition.h create mode 100644 app/node/metareader.cpp create mode 100644 app/node/metareader.h diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index d301deca5..85e367ba1 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -37,6 +37,8 @@ set(OLIVE_SOURCES node/inputarray.cpp node/keyframe.h node/keyframe.cpp + node/metareader.h + node/metareader.cpp node/node.h node/node.cpp node/output.h diff --git a/app/node/block/transition/CMakeLists.txt b/app/node/block/transition/CMakeLists.txt index 60ebb509e..844a4d90c 100644 --- a/app/node/block/transition/CMakeLists.txt +++ b/app/node/block/transition/CMakeLists.txt @@ -19,6 +19,8 @@ add_subdirectory(diptoblack) set(OLIVE_SOURCES ${OLIVE_SOURCES} + node/block/transition/externaltransition.h + node/block/transition/externaltransition.cpp node/block/transition/transition.h node/block/transition/transition.cpp PARENT_SCOPE diff --git a/app/node/block/transition/externaltransition.cpp b/app/node/block/transition/externaltransition.cpp new file mode 100644 index 000000000..107b47f55 --- /dev/null +++ b/app/node/block/transition/externaltransition.cpp @@ -0,0 +1,64 @@ +#include "externaltransition.h" + +ExternalTransition::ExternalTransition(const QString &xml_meta_filename) : + meta_(xml_meta_filename) +{ + foreach (NodeInput* input, meta_.inputs()) { + AddInput(input); + } +} + +Node *ExternalTransition::copy() const +{ + return new ExternalTransition(meta_.filename()); +} + +QString ExternalTransition::Name() const +{ + return meta_.Name(); +} + +QString ExternalTransition::id() const +{ + return meta_.id(); +} + +QString ExternalTransition::Category() const +{ + return meta_.Category(); +} + +QString ExternalTransition::Description() const +{ + return meta_.Description(); +} + +void ExternalTransition::Retranslate() +{ + meta_.Retranslate(); +} + +bool ExternalTransition::IsAccelerated() const +{ + return true; +} + +QString ExternalTransition::AcceleratedCodeVertex() const +{ + return meta_.vert_code(); +} + +QString ExternalTransition::AcceleratedCodeFragment() const +{ + return meta_.frag_code(); +} + +int ExternalTransition::AcceleratedCodeIterations() const +{ + return meta_.iterations(); +} + +NodeInput *ExternalTransition::AcceleratedCodeIterativeInput() const +{ + return meta_.iteration_input(); +} diff --git a/app/node/block/transition/externaltransition.h b/app/node/block/transition/externaltransition.h new file mode 100644 index 000000000..153e04b99 --- /dev/null +++ b/app/node/block/transition/externaltransition.h @@ -0,0 +1,32 @@ +#ifndef EXTERNALTRANSITION_H +#define EXTERNALTRANSITION_H + +#include "transition.h" + +#include "node/metareader.h" + +class ExternalTransition : public TransitionBlock +{ +public: + ExternalTransition(const QString& xml_meta_filename); + + virtual Node* copy() const override; + + virtual QString Name() const override; + virtual QString id() const override; + virtual QString Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual bool IsAccelerated() const override; + virtual QString AcceleratedCodeVertex() const override; + virtual QString AcceleratedCodeFragment() const override; + virtual int AcceleratedCodeIterations() const override; + virtual NodeInput* AcceleratedCodeIterativeInput() const override; + +private: + NodeMetaReader meta_; +}; + +#endif // EXTERNALTRANSITION_H diff --git a/app/node/external.cpp b/app/node/external.cpp index 0ec566631..7aa96cdd6 100644 --- a/app/node/external.cpp +++ b/app/node/external.cpp @@ -2,66 +2,42 @@ #include -#include "config/config.h" - ExternalNode::ExternalNode(const QString &xml_meta_filename) : - xml_filename_(xml_meta_filename), - iterations_(1), - iteration_input_(nullptr) + meta_(xml_meta_filename) { - QFile metadata_file(xml_filename_); - - if (metadata_file.open(QFile::ReadOnly)) { - QXmlStreamReader reader(&metadata_file); - - while (!reader.atEnd()) { - reader.readNext(); - - if (reader.isStartElement() && reader.name() == "effect") { - XMLReadEffect(&reader); - } - } - - metadata_file.close(); - } else { - qWarning() << "Failed to load node metadata file" << xml_filename_; + foreach (NodeInput* input, meta_.inputs()) { + AddInput(input); } } Node *ExternalNode::copy() const { - return new ExternalNode(xml_filename_); + return new ExternalNode(meta_.filename()); } QString ExternalNode::Name() const { - return GetStringForCurrentLanguage(&names_); + return meta_.Name(); } QString ExternalNode::id() const { - return id_; + return meta_.id(); } QString ExternalNode::Category() const { - return GetStringForCurrentLanguage(&categories_); + return meta_.Category(); } QString ExternalNode::Description() const { - return GetStringForCurrentLanguage(&descriptions_); + return meta_.Description(); } void ExternalNode::Retranslate() { - QMap >::const_iterator iterator; - - // Iterate through parameter language tables that we have - for (iterator=param_names_.begin();iterator!=param_names_.end();iterator++) { - NodeInput* this_input = static_cast(GetParameterWithID(iterator.key())); - this_input->set_name(GetStringForCurrentLanguage(&iterator.value())); - } + meta_.Retranslate(); } bool ExternalNode::IsAccelerated() const @@ -71,218 +47,20 @@ bool ExternalNode::IsAccelerated() const QString ExternalNode::AcceleratedCodeVertex() const { - return vert_code_; + return meta_.vert_code(); } QString ExternalNode::AcceleratedCodeFragment() const { - return frag_code_; + return meta_.frag_code(); } int ExternalNode::AcceleratedCodeIterations() const { - return iterations_; + return meta_.iterations(); } NodeInput *ExternalNode::AcceleratedCodeIterativeInput() const { - return iteration_input_; -} - -void ExternalNode::XMLReadLanguageString(QXmlStreamReader* reader, QMap* map) -{ - QString lang; - - // Traverse through name attributes for its language - QXmlStreamAttributes attrs = reader->attributes(); - foreach (const QXmlStreamAttribute& attr, attrs) { - if (attr.name() == "lang") { - lang = attr.value().toString(); - - // We don't recognize any other "name" attributes at this time - break; - } - } - - // Insert name with language into map - map->insert(lang, reader->readElementText().trimmed()); -} - -void ExternalNode::XMLReadEffect(QXmlStreamReader* reader) -{ - // Traverse through effect attributes for an ID - QXmlStreamAttributes attrs = reader->attributes(); - foreach (const QXmlStreamAttribute& attr, attrs) { - if (attr.name() == "id") { - id_ = attr.value().toString(); - - // We don't recognize any other "effect" attributes at this time - break; - } - } - - if (id_.isEmpty()) { - qWarning() << "Effect metadata" << xml_filename_ << "has no ID"; - return; - } - - // Continue reading for other metadata - while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "effect")) { - reader->readNext(); - - if (reader->isStartElement()) { - if (reader->name() == "name") { - // Pick up name - XMLReadLanguageString(reader, &names_); - } else if (reader->name() == "category") { - // Pick up category - XMLReadLanguageString(reader, &categories_); - } else if (reader->name() == "description") { - // Pick up description - XMLReadLanguageString(reader, &descriptions_); - } else if (reader->name() == "iterations") { - // Pick up iterations - XMLReadIterations(reader); - } else if (reader->name() == "fragment") { - // Pick up fragment shader code - XMLReadShader(reader, frag_code_); - } else if (reader->name() == "vertex") { - // Pick up vertex shader code - XMLReadShader(reader, vert_code_); - } else if (reader->name() == "param") { - // Pick up a parameter - XMLReadParam(reader); - } - } - } -} - -void ExternalNode::XMLReadIterations(QXmlStreamReader* reader) -{ - int iteration_pickup = reader->readElementText().toInt(); - - if (iterations_ > 0) { - iterations_ = iteration_pickup; - } else { - // If the iteration value is invalid, don't set it, print an error insteead - qWarning() << "Invalid iteration number in" << xml_filename_ << "- setting to default (1)"; - } -} - -void ExternalNode::XMLReadParam(QXmlStreamReader *reader) -{ - QString param_id; - NodeParam::DataType param_type = NodeParam::kAny; - bool is_iterative = false; - - // Traverse through parameter attributes for an ID - QXmlStreamAttributes attrs = reader->attributes(); - foreach (const QXmlStreamAttribute& attr, attrs) { - if (attr.name() == "id") { - param_id = attr.value().toString(); - } else if (attr.name() == "type") { - param_type = NodeParam::StringToDataType(attr.value().toString()); - } else if (attr.name() == "iterative_input") { - is_iterative = true; - } - } - - if (param_id.isEmpty()) { - qWarning() << "Effect metadata" << xml_filename_ << "contains a parameter with no ID - parameter was not added"; - return; - } - - NodeInput* input = new NodeInput(param_id); - input->set_data_type(param_type); - - if (is_iterative) { - iteration_input_ = input; - } - - // Traverse through param contents for more data - while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "param")) { - reader->readNext(); - - if (reader->isStartElement()) { - // NOTE: readElementText() returns a string, but for number types (which min and max apply to), QVariant will - // convert them automatically - if (reader->name() == "name") { - - // See if we've already made a table for this param before - if (!param_names_.contains(param_id)) { - param_names_.insert(param_id, QMap()); - } - - // Insert language into table - XMLReadLanguageString(reader, ¶m_names_[param_id]); - - } else if (reader->name() == "default") { - input->set_value_at_time(0, reader->readElementText()); - } else if (reader->name() == "min") { - input->set_minimum(reader->readElementText()); - } else if (reader->name() == "max") { - input->set_maximum(reader->readElementText()); - } - } - } - - AddInput(input); -} - -void ExternalNode::XMLReadShader(QXmlStreamReader *reader, QString &destination) -{ - QString code_url; - - // Traverse through parameter attributes for an ID - QXmlStreamAttributes attrs = reader->attributes(); - foreach (const QXmlStreamAttribute& attr, attrs) { - if (attr.name() == "url") { - code_url = attr.value().toString(); - - // We don't recognize any other "shader" attributes at this time - break; - } - } - - // Add code in file from URL - if (!code_url.isEmpty()) { - destination.append(ReadFileAsString(code_url)); - } - - // Add any code that's inline in the XML - QString element_text = reader->readElementText().trimmed(); - if (!element_text.isEmpty()) { - destination.append(element_text); - } -} - -QString ExternalNode::GetStringForCurrentLanguage(const QMap *language_map) -{ - if (language_map->isEmpty()) { - // There are no entries for this map, this must be an empty string - return QString(); - } - - // Get current language config - QString language = Config::Current()["Language"].toString(); - - // See if our map has an exact language match - QString str_for_lang = language_map->value(language); - if (!str_for_lang.isEmpty()) { - return str_for_lang; - } - - // If not, try to find a match with the same language but not the same derivation - QString base_lang = language.split('_').first(); - QList available_languages = language_map->keys(); - foreach (const QString& l, available_languages) { - if (l.startsWith(base_lang)) { - // This is the same language, so we can return this - return language_map->value(l); - } - } - - // We couldn't find an exact or close match, just return the first in the list - // (assume a string in the wrong language is better than no string at all) - return language_map->first(); + return meta_.iteration_input(); } diff --git a/app/node/external.h b/app/node/external.h index d5c35a24c..d3a2b15fd 100644 --- a/app/node/external.h +++ b/app/node/external.h @@ -4,6 +4,7 @@ #include #include "node.h" +#include "metareader.h" /** * @brief A node generated from an external XML metadata file @@ -29,28 +30,7 @@ public: virtual NodeInput* AcceleratedCodeIterativeInput() const override; private: - void XMLReadLanguageString(QXmlStreamReader* reader, QMap* map); - void XMLReadEffect(QXmlStreamReader *reader); - void XMLReadIterations(QXmlStreamReader* reader); - void XMLReadParam(QXmlStreamReader* reader); - void XMLReadShader(QXmlStreamReader* reader, QString& destination); - - static QString GetStringForCurrentLanguage(const QMap *language_map); - - QString xml_filename_; - - QMap names_; - QMap descriptions_; - QMap categories_; - QMap > param_names_; - - QString id_; - - QString frag_code_; - QString vert_code_; - - int iterations_; - NodeInput* iteration_input_; + NodeMetaReader meta_; }; #endif // EXTERNALNODE_H diff --git a/app/node/metareader.cpp b/app/node/metareader.cpp new file mode 100644 index 000000000..36e50b377 --- /dev/null +++ b/app/node/metareader.cpp @@ -0,0 +1,299 @@ +#include "metareader.h" + +#include + +#include "config/config.h" +#include "node.h" + +NodeMetaReader::NodeMetaReader(const QString &xml_meta_filename) : + xml_filename_(xml_meta_filename), + iterations_(1), + iteration_input_(nullptr) +{ + QFile metadata_file(xml_filename_); + + if (metadata_file.open(QFile::ReadOnly)) { + QXmlStreamReader reader(&metadata_file); + + while (!reader.atEnd()) { + reader.readNext(); + + if (reader.isStartElement() && reader.name() == "effect") { + XMLReadEffect(&reader); + } + } + + metadata_file.close(); + } else { + qWarning() << "Failed to load node metadata file" << xml_filename_; + } +} + +QString NodeMetaReader::Name() const +{ + return GetStringForCurrentLanguage(&names_); +} + +const QString &NodeMetaReader::id() const +{ + return id_; +} + +QString NodeMetaReader::Category() const +{ + return GetStringForCurrentLanguage(&categories_); +} + +QString NodeMetaReader::Description() const +{ + return GetStringForCurrentLanguage(&descriptions_); +} + +const QString &NodeMetaReader::filename() const +{ + return xml_filename_; +} + +const QString &NodeMetaReader::frag_code() const +{ + return frag_code_; +} + +const QString &NodeMetaReader::vert_code() const +{ + return vert_code_; +} + +const int &NodeMetaReader::iterations() const +{ + return iterations_; +} + +NodeInput *NodeMetaReader::iteration_input() const +{ + return iteration_input_; +} + +const QList &NodeMetaReader::inputs() const +{ + return inputs_; +} + +void NodeMetaReader::Retranslate() +{ + QMap >::const_iterator iterator; + + // Iterate through parameter language tables that we have + for (iterator=param_names_.begin();iterator!=param_names_.end();iterator++) { + NodeInput* this_input = GetInputWithID(iterator.key()); + this_input->set_name(GetStringForCurrentLanguage(&iterator.value())); + } +} + +void NodeMetaReader::XMLReadLanguageString(QXmlStreamReader* reader, QMap* map) +{ + QString lang; + + // Traverse through name attributes for its language + QXmlStreamAttributes attrs = reader->attributes(); + foreach (const QXmlStreamAttribute& attr, attrs) { + if (attr.name() == "lang") { + lang = attr.value().toString(); + + // We don't recognize any other "name" attributes at this time + break; + } + } + + // Insert name with language into map + map->insert(lang, reader->readElementText().trimmed()); +} + +void NodeMetaReader::XMLReadEffect(QXmlStreamReader* reader) +{ + // Traverse through effect attributes for an ID + QXmlStreamAttributes attrs = reader->attributes(); + foreach (const QXmlStreamAttribute& attr, attrs) { + if (attr.name() == "id") { + id_ = attr.value().toString(); + + // We don't recognize any other "effect" attributes at this time + break; + } + } + + if (id_.isEmpty()) { + qWarning() << "Effect metadata" << xml_filename_ << "has no ID"; + return; + } + + // Continue reading for other metadata + while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "effect")) { + reader->readNext(); + + if (reader->isStartElement()) { + if (reader->name() == "name") { + // Pick up name + XMLReadLanguageString(reader, &names_); + } else if (reader->name() == "category") { + // Pick up category + XMLReadLanguageString(reader, &categories_); + } else if (reader->name() == "description") { + // Pick up description + XMLReadLanguageString(reader, &descriptions_); + } else if (reader->name() == "iterations") { + // Pick up iterations + XMLReadIterations(reader); + } else if (reader->name() == "fragment") { + // Pick up fragment shader code + XMLReadShader(reader, frag_code_); + } else if (reader->name() == "vertex") { + // Pick up vertex shader code + XMLReadShader(reader, vert_code_); + } else if (reader->name() == "param") { + // Pick up a parameter + XMLReadParam(reader); + } + } + } +} + +void NodeMetaReader::XMLReadIterations(QXmlStreamReader* reader) +{ + int iteration_pickup = reader->readElementText().toInt(); + + if (iterations_ > 0) { + iterations_ = iteration_pickup; + } else { + // If the iteration value is invalid, don't set it, print an error insteead + qWarning() << "Invalid iteration number in" << xml_filename_ << "- setting to default (1)"; + } +} + +void NodeMetaReader::XMLReadParam(QXmlStreamReader *reader) +{ + QString param_id; + NodeParam::DataType param_type = NodeParam::kAny; + bool is_iterative = false; + + // Traverse through parameter attributes for an ID + QXmlStreamAttributes attrs = reader->attributes(); + foreach (const QXmlStreamAttribute& attr, attrs) { + if (attr.name() == "id") { + param_id = attr.value().toString(); + } else if (attr.name() == "type") { + param_type = NodeParam::StringToDataType(attr.value().toString()); + } else if (attr.name() == "iterative_input") { + is_iterative = true; + } + } + + if (param_id.isEmpty()) { + qWarning() << "Effect metadata" << xml_filename_ << "contains a parameter with no ID - parameter was not added"; + return; + } + + NodeInput* input = new NodeInput(param_id); + input->set_data_type(param_type); + + if (is_iterative) { + iteration_input_ = input; + } + + // Traverse through param contents for more data + while (!reader->atEnd() && !(reader->isEndElement() && reader->name() == "param")) { + reader->readNext(); + + if (reader->isStartElement()) { + // NOTE: readElementText() returns a string, but for number types (which min and max apply to), QVariant will + // convert them automatically + if (reader->name() == "name") { + + // See if we've already made a table for this param before + if (!param_names_.contains(param_id)) { + param_names_.insert(param_id, QMap()); + } + + // Insert language into table + XMLReadLanguageString(reader, ¶m_names_[param_id]); + + } else if (reader->name() == "default") { + input->set_value_at_time(0, reader->readElementText()); + } else if (reader->name() == "min") { + input->set_minimum(reader->readElementText()); + } else if (reader->name() == "max") { + input->set_maximum(reader->readElementText()); + } + } + } + + inputs_.append(input); +} + +void NodeMetaReader::XMLReadShader(QXmlStreamReader *reader, QString &destination) +{ + QString code_url; + + // Traverse through parameter attributes for an ID + QXmlStreamAttributes attrs = reader->attributes(); + foreach (const QXmlStreamAttribute& attr, attrs) { + if (attr.name() == "url") { + code_url = attr.value().toString(); + + // We don't recognize any other "shader" attributes at this time + break; + } + } + + // Add code in file from URL + if (!code_url.isEmpty()) { + destination.append(Node::ReadFileAsString(code_url)); + } + + // Add any code that's inline in the XML + QString element_text = reader->readElementText().trimmed(); + if (!element_text.isEmpty()) { + destination.append(element_text); + } +} + +QString NodeMetaReader::GetStringForCurrentLanguage(const QMap *language_map) +{ + if (language_map->isEmpty()) { + // There are no entries for this map, this must be an empty string + return QString(); + } + + // Get current language config + QString language = Config::Current()["Language"].toString(); + + // See if our map has an exact language match + QString str_for_lang = language_map->value(language); + if (!str_for_lang.isEmpty()) { + return str_for_lang; + } + + // If not, try to find a match with the same language but not the same derivation + QString base_lang = language.split('_').first(); + QList available_languages = language_map->keys(); + foreach (const QString& l, available_languages) { + if (l.startsWith(base_lang)) { + // This is the same language, so we can return this + return language_map->value(l); + } + } + + // We couldn't find an exact or close match, just return the first in the list + // (assume a string in the wrong language is better than no string at all) + return language_map->first(); +} + +NodeInput *NodeMetaReader::GetInputWithID(const QString &id) const +{ + foreach (NodeInput* input, inputs_) { + if (input->id() == id) { + return input; + } + } + return nullptr; +} diff --git a/app/node/metareader.h b/app/node/metareader.h new file mode 100644 index 000000000..ae5835fe8 --- /dev/null +++ b/app/node/metareader.h @@ -0,0 +1,61 @@ +#ifndef NODEMETAREADER_H +#define NODEMETAREADER_H + +#include +#include +#include + +#include "input.h" + +class NodeMetaReader +{ +public: + NodeMetaReader(const QString& xml_meta_filename); + + QString Name() const; + const QString& id() const; + QString Category() const; + QString Description() const; + + const QString& filename() const; + + const QString& frag_code() const; + const QString& vert_code() const; + + const int& iterations() const; + NodeInput* iteration_input() const; + + const QList& inputs() const; + + void Retranslate(); + +private: + void XMLReadLanguageString(QXmlStreamReader* reader, QMap* map); + void XMLReadEffect(QXmlStreamReader *reader); + void XMLReadIterations(QXmlStreamReader* reader); + void XMLReadParam(QXmlStreamReader* reader); + void XMLReadShader(QXmlStreamReader* reader, QString& destination); + + static QString GetStringForCurrentLanguage(const QMap *language_map); + + NodeInput* GetInputWithID(const QString& id) const; + + QString xml_filename_; + + QMap names_; + QMap descriptions_; + QMap categories_; + QMap > param_names_; + + QString id_; + + QString frag_code_; + QString vert_code_; + + int iterations_; + NodeInput* iteration_input_; + + QList inputs_; +}; + +#endif // NODEMETAREADER_H diff --git a/app/node/node.h b/app/node/node.h index 4f4978bcf..b5917fc87 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -287,6 +287,8 @@ public: void SetPosition(const QPointF& pos); + static QString ReadFileAsString(const QString& filename); + protected: void AddInput(NodeInput* input); @@ -296,8 +298,6 @@ protected: virtual void DependentEdgeChanged(NodeInput* from); - static QString ReadFileAsString(const QString& filename); - public slots: signals: