diff --git a/python/core/processing/qgsprocessingmodelalgorithm.sip b/python/core/processing/qgsprocessingmodelalgorithm.sip index 72143a6e081..90b613b8aac 100644 --- a/python/core/processing/qgsprocessingmodelalgorithm.sip +++ b/python/core/processing/qgsprocessingmodelalgorithm.sip @@ -139,6 +139,20 @@ class QgsProcessingModelAlgorithm : QgsProcessingAlgorithm .. seealso:: setOutputChildId() %End + QVariant toVariant() const; +%Docstring + Saves this source to a QVariant. +.. seealso:: loadVariant() + :rtype: QVariant +%End + + bool loadVariant( const QVariantMap &map ); +%Docstring + Loads this source from a QVariantMap. +.. seealso:: toVariant() + :rtype: bool +%End + }; class Component @@ -192,6 +206,18 @@ Copies are protected to avoid slicing %End + void saveCommonProperties( QVariantMap &map ) const; +%Docstring + Saves the component properties to a QVariantMap. +.. seealso:: restoreCommonProperties() +%End + + void restoreCommonProperties( const QVariantMap &map ); +%Docstring + Restores the component properties from a QVariantMap. +.. seealso:: saveCommonProperties() +%End + }; class ModelParameter : QgsProcessingModelAlgorithm::Component @@ -227,6 +253,20 @@ Copies are protected to avoid slicing .. seealso:: parameterName() %End + QVariant toVariant() const; +%Docstring + Saves this parameter to a QVariant. +.. seealso:: loadVariant() + :rtype: QVariant +%End + + bool loadVariant( const QVariantMap &map ); +%Docstring + Loads this parameter from a QVariantMap. +.. seealso:: toVariant() + :rtype: bool +%End + }; @@ -273,6 +313,20 @@ Copies are protected to avoid slicing .. seealso:: outputName() %End + QVariant toVariant() const; +%Docstring + Saves this output to a QVariant. +.. seealso:: loadVariant() + :rtype: QVariant +%End + + bool loadVariant( const QVariantMap &map ); +%Docstring + Loads this output from a QVariantMap. +.. seealso:: toVariant() + :rtype: bool +%End + }; class ChildAlgorithm : QgsProcessingModelAlgorithm::Component @@ -461,6 +515,20 @@ Copies are protected to avoid slicing .. seealso:: modelOutputs() %End + QVariant toVariant() const; +%Docstring + Saves this child to a QVariant. +.. seealso:: loadVariant() + :rtype: QVariant +%End + + bool loadVariant( const QVariant &child ); +%Docstring + Loads this child from a QVariant. +.. seealso:: toVariant() + :rtype: bool +%End + }; QgsProcessingModelAlgorithm( const QString &name = QString(), const QString &group = QString() ); @@ -484,6 +552,18 @@ Copies are protected to avoid slicing virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const; + void setName( const QString &name ); +%Docstring + Sets the model ``name``. +.. seealso:: name() +%End + + void setGroup( const QString &group ); +%Docstring + Sets the model ``group``. +.. seealso:: group() +%End + QMap childAlgorithms() const; %Docstring Returns the map of child algorithms contained in the model. The keys @@ -651,6 +731,20 @@ Copies are protected to avoid slicing :rtype: QgsProcessingModelAlgorithm.ModelParameter %End + bool toFile( const QString &path ) const; +%Docstring + Writes the model to a file, at the specified ``path``. +.. seealso:: fromFile() + :rtype: bool +%End + + bool fromFile( const QString &path ); +%Docstring + Reads the model from a file, at the specified ``path``. +.. seealso:: toFile() + :rtype: bool +%End + }; diff --git a/python/plugins/processing/modeler/AddModelFromFileAction.py b/python/plugins/processing/modeler/AddModelFromFileAction.py index e69a90cadde..b62cbab7e02 100644 --- a/python/plugins/processing/modeler/AddModelFromFileAction.py +++ b/python/plugins/processing/modeler/AddModelFromFileAction.py @@ -56,22 +56,16 @@ class AddModelFromFileAction(ToolboxAction): self.tr('Open model', 'AddModelFromFileAction'), lastDir, self.tr('Processing model files (*.model *.MODEL)', 'AddModelFromFileAction')) if filename: - try: - settings.setValue('Processing/lastModelsDir', - QFileInfo(filename).absoluteDir().absolutePath()) + settings.setValue('Processing/lastModelsDir', + QFileInfo(filename).absoluteDir().absolutePath()) - ModelerAlgorithm.fromFile(filename) - except WrongModelException: + alg = ModelerAlgorithm() + if not alg.fromFile(filename): QMessageBox.warning( self.toolbox, self.tr('Error reading model', 'AddModelFromFileAction'), self.tr('The selected file does not contain a valid model', 'AddModelFromFileAction')) return - except: - QMessageBox.warning(self.toolbox, - self.tr('Error reading model', 'AddModelFromFileAction'), - self.tr('Cannot read file', 'AddModelFromFileAction')) - return destFilename = os.path.join(ModelerUtils.modelsFolders()[0], os.path.basename(filename)) shutil.copyfile(filename, destFilename) QgsApplication.processingRegistry().providerById('model').refreshAlgorithms() diff --git a/python/plugins/processing/modeler/ModelerAlgorithm.py b/python/plugins/processing/modeler/ModelerAlgorithm.py index 899c5c1731d..02405d00aaf 100644 --- a/python/plugins/processing/modeler/ModelerAlgorithm.py +++ b/python/plugins/processing/modeler/ModelerAlgorithm.py @@ -365,15 +365,6 @@ class ModelerAlgorithm(QgsProcessingModelAlgorithm): model._name = model.modeler_name return model - @staticmethod - def fromFile(filename): - with open(filename) as f: - s = f.read() - alg = ModelerAlgorithm.fromJson(s) - if alg: - alg.descriptionFile = filename - return alg - def toPython(self): s = ['##%s=name' % self.name()] for param in list(self.parameterComponents().values()): diff --git a/python/plugins/processing/modeler/ModelerAlgorithmProvider.py b/python/plugins/processing/modeler/ModelerAlgorithmProvider.py index 2382dd14804..a2a8a7beee0 100644 --- a/python/plugins/processing/modeler/ModelerAlgorithmProvider.py +++ b/python/plugins/processing/modeler/ModelerAlgorithmProvider.py @@ -27,10 +27,13 @@ __revision__ = '$Format:%H$' import os +from qgis.PyQt.QtXml import QDomDocument + from qgis.core import (QgsApplication, QgsProcessingProvider, QgsMessageLog, - QgsProcessingUtils) + QgsProcessingUtils, + QgsXmlUtils) from processing.core.ProcessingConfig import ProcessingConfig, Setting from processing.modeler.ModelerUtils import ModelerUtils @@ -98,13 +101,15 @@ class ModelerAlgorithmProvider(QgsProcessingProvider): return for path, subdirs, files in os.walk(folder): for descriptionFile in files: - if descriptionFile.endswith('model'): + if descriptionFile.endswith('model3'): try: fullpath = os.path.join(path, descriptionFile) - alg = ModelerAlgorithm.fromFile(fullpath) - if alg.name(): - alg.descriptionFile = fullpath - self.algs.append(alg) + + alg = ModelerAlgorithm() + if alg.fromFile(fullpath): + if alg.name(): + alg.descriptionFile = fullpath + self.algs.append(alg) else: QgsMessageLog.logMessage(self.tr('Could not load model {0}', 'ModelerAlgorithmProvider').format(descriptionFile), self.tr('Processing'), QgsMessageLog.CRITICAL) diff --git a/python/plugins/processing/modeler/ModelerDialog.py b/python/plugins/processing/modeler/ModelerDialog.py index d4c9c751408..63256fed532 100644 --- a/python/plugins/processing/modeler/ModelerDialog.py +++ b/python/plugins/processing/modeler/ModelerDialog.py @@ -42,7 +42,8 @@ from qgis.core import (QgsApplication, QgsSettings, QgsMessageLog, QgsProcessingUtils, - QgsProcessingModelAlgorithm) + QgsProcessingModelAlgorithm, + QgsXmlUtils) from qgis.gui import QgsMessageBar from processing.gui.HelpEditionDialog import HelpEditionDialog from processing.gui.AlgorithmDialog import AlgorithmDialog @@ -52,6 +53,7 @@ from processing.modeler.ModelerParametersDialog import ModelerParametersDialog from processing.modeler.ModelerUtils import ModelerUtils from processing.modeler.ModelerScene import ModelerScene from processing.modeler.WrongModelException import WrongModelException +from qgis.PyQt.QtXml import QDomDocument pluginPath = os.path.split(os.path.dirname(__file__))[0] WIDGET, BASE = uic.loadUiType( @@ -435,25 +437,21 @@ class ModelerDialog(BASE, WIDGET): self, self.tr('Warning'), self.tr('Please enter group and model names before saving') ) return - self.model._name = str(self.textName.text()) - self.model._group = str(self.textGroup.text()) + self.model.setName(str(self.textName.text())) + self.model.setGroup(str(self.textGroup.text())) if self.model.descriptionFile is not None and not saveAs: filename = self.model.descriptionFile else: filename, filter = QFileDialog.getSaveFileName(self, self.tr('Save Model'), ModelerUtils.modelsFolders()[0], - self.tr('Processing models (*.model)')) + self.tr('Processing models (*.model3)')) if filename: - if not filename.endswith('.model'): - filename += '.model' + if not filename.endswith('.model3'): + filename += '.model3' self.model.descriptionFile = filename if filename: - text = self.model.toJson() - try: - with codecs.open(filename, 'w', encoding='utf-8') as fout: - fout.write(text) - except: + if not self.model.toFile(filename): if saveAs: QMessageBox.warning(self, self.tr('I/O error'), self.tr('Unable to save edits. Reason:\n {0}').format(str(sys.exc_info()[1]))) @@ -475,28 +473,22 @@ class ModelerDialog(BASE, WIDGET): ModelerUtils.modelsFolders()[0], self.tr('Processing models (*.model *.MODEL)')) if filename: - try: - alg = ModelerAlgorithm.fromFile(filename) + alg = ModelerAlgorithm() + if alg.fromFile(filename): self.model = alg - self.textGroup.setText(alg._group) - self.textName.setText(alg._name) + self.textGroup.setText(alg.group()) + self.textName.setText(alg.name()) self.repaintModel() self.view.centerOn(0, 0) self.hasChanged = False - except WrongModelException as e: - QgsMessageLog.logMessage(self.tr('Could not load model {0}\n{1}').format(filename, e.msg), + else: + QgsMessageLog.logMessage(self.tr('Could not load model {0}').format(filename), self.tr('Processing'), QgsMessageLog.CRITICAL) QMessageBox.critical(self, self.tr('Could not open model'), self.tr('The selected model could not be loaded.\n' 'See the log for more information.')) - except Exception as e: - QgsMessageLog.logMessage(self.tr('Could not load model {0}\n{1}').format(filename, e.args[0]), - self.tr('Processing'), QgsMessageLog.CRITICAL) - QMessageBox.critical(self, self.tr('Could not open model'), - self.tr('The selected model could not be loaded.\n' - 'See the log for more information.')) def repaintModel(self, controls=True): self.scene = ModelerScene(self, dialog=self) diff --git a/src/core/processing/qgsprocessingmodelalgorithm.cpp b/src/core/processing/qgsprocessingmodelalgorithm.cpp index 7ae6a21daac..b4f9c3c1be3 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.cpp +++ b/src/core/processing/qgsprocessingmodelalgorithm.cpp @@ -17,6 +17,9 @@ #include "qgsprocessingmodelalgorithm.h" #include "qgsprocessingregistry.h" +#include "qgsxmlutils.h" +#include +#include QgsProcessingModelAlgorithm::ChildAlgorithm::ChildAlgorithm( const QString &algorithmId ) : mAlgorithmId( algorithmId ) @@ -78,6 +81,22 @@ QgsProcessingModelAlgorithm::Component::Component( const QString &description ) : mDescription( description ) {} +void QgsProcessingModelAlgorithm::Component::saveCommonProperties( QVariantMap &map ) const +{ + map.insert( QStringLiteral( "component_pos_x" ), mPosition.x() ); + map.insert( QStringLiteral( "component_pos_y" ), mPosition.y() ); + map.insert( QStringLiteral( "component_description" ), mDescription ); +} + +void QgsProcessingModelAlgorithm::Component::restoreCommonProperties( const QVariantMap &map ) +{ + QPointF pos; + pos.setX( map.value( QStringLiteral( "component_pos_x" ) ).toDouble() ); + pos.setY( map.value( QStringLiteral( "component_pos_y" ) ).toDouble() ); + mPosition = pos; + mDescription = map.value( QStringLiteral( "component_description" ) ).toString(); +} + QStringList QgsProcessingModelAlgorithm::ChildAlgorithm::dependencies() const { return mDependencies; @@ -113,6 +132,77 @@ void QgsProcessingModelAlgorithm::ChildAlgorithm::setModelOutputs( const QMap::const_iterator paramIt = mParams.constBegin(); + for ( ; paramIt != mParams.constEnd(); ++paramIt ) + { + paramMap.insert( paramIt.key(), paramIt.value().toVariant() ); + } + map.insert( "params", paramMap ); + + QVariantMap outputMap; + QMap< QString, QgsProcessingModelAlgorithm::ModelOutput >::const_iterator outputIt = mModelOutputs.constBegin(); + for ( ; outputIt != mModelOutputs.constEnd(); ++outputIt ) + { + outputMap.insert( outputIt.key(), outputIt.value().toVariant() ); + } + map.insert( "outputs", outputMap ); + + return map; +} + +bool QgsProcessingModelAlgorithm::ChildAlgorithm::loadVariant( const QVariant &child ) +{ + QVariantMap map = child.toMap(); + + mId = map.value( QStringLiteral( "id" ) ).toString(); + mAlgorithmId = map.value( QStringLiteral( "alg_id" ) ).toString(); + mActive = map.value( QStringLiteral( "active" ) ).toBool(); + mDependencies = map.value( QStringLiteral( "dependencies" ) ).toStringList(); + mParametersCollapsed = map.value( QStringLiteral( "parameters_collapsed" ) ).toBool(); + mOutputsCollapsed = map.value( QStringLiteral( "outputs_collapsed" ) ).toBool(); + + restoreCommonProperties( map ); + + mParams.clear(); + QVariantMap paramMap = map.value( QStringLiteral( "params" ) ).toMap(); + QVariantMap::const_iterator paramIt = paramMap.constBegin(); + for ( ; paramIt != paramMap.constEnd(); ++paramIt ) + { + ChildParameterSource param; + if ( !param.loadVariant( paramIt.value().toMap() ) ) + return false; + + mParams.insert( paramIt.key(), param ); + } + + mModelOutputs.clear(); + QVariantMap outputMap = map.value( QStringLiteral( "outputs" ) ).toMap(); + QVariantMap::const_iterator outputIt = outputMap.constBegin(); + for ( ; outputIt != outputMap.constEnd(); ++outputIt ) + { + ModelOutput output; + if ( !output.loadVariant( outputIt.value().toMap() ) ) + return false; + + mModelOutputs.insert( outputIt.key(), output ); + } + + return true; +} + bool QgsProcessingModelAlgorithm::ChildAlgorithm::parametersCollapsed() const { return mParametersCollapsed; @@ -201,6 +291,16 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa return QVariantMap(); } +void QgsProcessingModelAlgorithm::setName( const QString &name ) +{ + mModelName = name; +} + +void QgsProcessingModelAlgorithm::setGroup( const QString &group ) +{ + mModelGroup = group; +} + QMap QgsProcessingModelAlgorithm::childAlgorithms() const { return mChildAlgorithms; @@ -227,6 +327,99 @@ QgsProcessingModelAlgorithm::ModelParameter &QgsProcessingModelAlgorithm::parame return mParameterComponents[ name ]; } +QVariant QgsProcessingModelAlgorithm::toVariant() const +{ + QVariantMap map; + map.insert( QStringLiteral( "model_name" ), mModelName ); + map.insert( QStringLiteral( "model_group" ), mModelGroup ); + + QVariantMap childMap; + QMap< QString, ChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin(); + for ( ; childIt != mChildAlgorithms.constEnd(); ++childIt ) + { + childMap.insert( childIt.key(), childIt.value().toVariant() ); + } + map.insert( "children", childMap ); + + QVariantMap paramMap; + QMap< QString, ModelParameter >::const_iterator paramIt = mParameterComponents.constBegin(); + for ( ; paramIt != mParameterComponents.constEnd(); ++paramIt ) + { + paramMap.insert( paramIt.key(), paramIt.value().toVariant() ); + } + map.insert( "parameters", paramMap ); + + return map; +} + +bool QgsProcessingModelAlgorithm::loadVariant( const QVariant &model ) +{ + QVariantMap map = model.toMap(); + + mModelName = map.value( QStringLiteral( "model_name" ) ).toString(); + mModelGroup = map.value( QStringLiteral( "model_group" ) ).toString(); + + mChildAlgorithms.clear(); + QVariantMap childMap = map.value( QStringLiteral( "children" ) ).toMap(); + QVariantMap::const_iterator childIt = childMap.constBegin(); + for ( ; childIt != childMap.constEnd(); ++childIt ) + { + ChildAlgorithm child; + if ( !child.loadVariant( childIt.value() ) ) + return false; + + mChildAlgorithms.insert( child.childId(), child ); + } + + mParameterComponents.clear(); + QVariantMap paramMap = map.value( QStringLiteral( "parameters" ) ).toMap(); + QVariantMap::const_iterator paramIt = paramMap.constBegin(); + for ( ; paramIt != paramMap.constEnd(); ++paramIt ) + { + ModelParameter param; + if ( !param.loadVariant( paramIt.value().toMap() ) ) + return false; + + mParameterComponents.insert( param.parameterName(), param ); + } + + return true; +} + +bool QgsProcessingModelAlgorithm::toFile( const QString &path ) const +{ + QDomDocument doc = QDomDocument( "model" ); + QDomElement elem = QgsXmlUtils::writeVariant( toVariant(), doc ); + doc.appendChild( elem ); + + QFile file( path ); + if ( file.open( QFile::WriteOnly | QFile::Truncate ) ) + { + QTextStream stream( &file ); + doc.save( stream, 2 ); + file.close(); + return true; + } + return false; +} + +bool QgsProcessingModelAlgorithm::fromFile( const QString &path ) +{ + QDomDocument doc; + + QFile file( path ); + if ( file.open( QFile::ReadOnly ) ) + { + if ( !doc.setContent( &file ) ) + return false; + + file.close(); + } + + QVariant props = QgsXmlUtils::readVariant( doc.firstChildElement() ); + return loadVariant( props ); +} + void QgsProcessingModelAlgorithm::setChildAlgorithms( const QMap &childAlgorithms ) { mChildAlgorithms = childAlgorithms; @@ -484,13 +677,91 @@ QgsProcessingModelAlgorithm::ChildParameterSource::Source QgsProcessingModelAlgo return mSource; } +QVariant QgsProcessingModelAlgorithm::ChildParameterSource::toVariant() const +{ + QVariantMap map; + map.insert( QStringLiteral( "source" ), mSource ); + switch ( mSource ) + { + case ModelParameter: + map.insert( QStringLiteral( "parameter_name" ), mParameterName ); + break; + + case ChildOutput: + map.insert( QStringLiteral( "child_id" ), mChildId ); + map.insert( QStringLiteral( "output_name" ), mOutputName ); + break; + + case StaticValue: + map.insert( QStringLiteral( "static_value" ), mStaticValue ); + break; + } + return map; +} + +bool QgsProcessingModelAlgorithm::ChildParameterSource::loadVariant( const QVariantMap &map ) +{ + mSource = static_cast< Source >( map.value( QStringLiteral( "source" ) ).toInt() ); + switch ( mSource ) + { + case ModelParameter: + mParameterName = map.value( QStringLiteral( "parameter_name" ) ).toString(); + break; + + case ChildOutput: + mChildId = map.value( QStringLiteral( "child_id" ) ).toString(); + mOutputName = map.value( QStringLiteral( "output_name" ) ).toString(); + break; + + case StaticValue: + mStaticValue = map.value( QStringLiteral( "static_value" ) ); + break; + } + return true; +} + QgsProcessingModelAlgorithm::ModelOutput::ModelOutput( const QString &description ) : QgsProcessingModelAlgorithm::Component( description ) {} +QVariant QgsProcessingModelAlgorithm::ModelOutput::toVariant() const +{ + QVariantMap map; + map.insert( QStringLiteral( "child_id" ), mChildId ); + map.insert( QStringLiteral( "output_name" ), mOutputName ); + saveCommonProperties( map ); + return map; +} + +bool QgsProcessingModelAlgorithm::ModelOutput::loadVariant( const QVariantMap &map ) +{ + mChildId = map.value( QStringLiteral( "child_id" ) ).toString(); + mOutputName = map.value( QStringLiteral( "output_name" ) ).toString(); + restoreCommonProperties( map ); + return true; +} + QgsProcessingModelAlgorithm::ModelParameter::ModelParameter( const QString ¶meterName ) : QgsProcessingModelAlgorithm::Component() , mParameterName( parameterName ) { } + +QVariant QgsProcessingModelAlgorithm::ModelParameter::toVariant() const +{ + QVariantMap map; + map.insert( QStringLiteral( "name" ), mParameterName ); + + //TODO - parameter definition + + saveCommonProperties( map ); + return map; +} + +bool QgsProcessingModelAlgorithm::ModelParameter::loadVariant( const QVariantMap &map ) +{ + mParameterName = map.value( QStringLiteral( "name" ) ).toString(); + restoreCommonProperties( map ); + return true; +} diff --git a/src/core/processing/qgsprocessingmodelalgorithm.h b/src/core/processing/qgsprocessingmodelalgorithm.h index 4ab843f8720..a9a27a142eb 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.h +++ b/src/core/processing/qgsprocessingmodelalgorithm.h @@ -140,6 +140,18 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm */ void setOutputName( const QString &name ) { mOutputName = name; mSource = ChildOutput; } + /** + * Saves this source to a QVariant. + * \see loadVariant() + */ + QVariant toVariant() const; + + /** + * Loads this source from a QVariantMap. + * \see toVariant() + */ + bool loadVariant( const QVariantMap &map ); + private: Source mSource = StaticValue; @@ -194,6 +206,18 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm //! Copies are protected to avoid slicing Component &operator=( const QgsProcessingModelAlgorithm::Component &other ) = default; + /** + * Saves the component properties to a QVariantMap. + * \see restoreCommonProperties() + */ + void saveCommonProperties( QVariantMap &map ) const; + + /** + * Restores the component properties from a QVariantMap. + * \see saveCommonProperties() + */ + void restoreCommonProperties( const QVariantMap &map ); + private: //! Position of component within model @@ -232,6 +256,18 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm */ void setParameterName( const QString &name ) { mParameterName = name; } + /** + * Saves this parameter to a QVariant. + * \see loadVariant() + */ + QVariant toVariant() const; + + /** + * Loads this parameter from a QVariantMap. + * \see toVariant() + */ + bool loadVariant( const QVariantMap &map ); + private: QString mParameterName; @@ -277,6 +313,18 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm */ void setOutputName( const QString &name ) { mOutputName = name; } + /** + * Saves this output to a QVariant. + * \see loadVariant() + */ + QVariant toVariant() const; + + /** + * Loads this output from a QVariantMap. + * \see toVariant() + */ + bool loadVariant( const QVariantMap &map ); + private: QString mChildId; @@ -456,6 +504,18 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm */ void setModelOutputs( const QMap &outputs ); + /** + * Saves this child to a QVariant. + * \see loadVariant() + */ + QVariant toVariant() const; + + /** + * Loads this child from a QVariant. + * \see toVariant() + */ + bool loadVariant( const QVariant &child ); + private: QString mId; @@ -494,6 +554,18 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + /** + * Sets the model \a name. + * \see name() + */ + void setName( const QString &name ); + + /** + * Sets the model \a group. + * \see group() + */ + void setGroup( const QString &group ); + /** * Returns the map of child algorithms contained in the model. The keys * are the child algorithm ids (see QgsProcessingModelAlgorithm::ChildAlgorithm::childId()). @@ -650,6 +722,18 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm */ QgsProcessingModelAlgorithm::ModelParameter ¶meterComponent( const QString &name ); + /** + * Writes the model to a file, at the specified \a path. + * \see fromFile() + */ + bool toFile( const QString &path ) const; + + /** + * Reads the model from a file, at the specified \a path. + * \see toFile() + */ + bool fromFile( const QString &path ); + private: QString mModelName; @@ -662,6 +746,24 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm void dependsOnChildAlgorithmsRecursive( const QString &childId, QSet &depends ) const; void dependentChildAlgorithmsRecursive( const QString &childId, QSet &depends ) const; + + /** + * Saves this model to a QVariantMap, wrapped in a QVariant. + * You can use QgsXmlUtils::writeVariant to save it to an XML document. + * + * \see loadVariant() + */ + QVariant toVariant() const; + + /** + * Loads this model from a QVariantMap, wrapped in a QVariant. + * You can use QgsXmlUtils::readVariant to load it from an XML document. + * + * \see toVariant() + */ + bool loadVariant( const QVariant &model ); + + friend class TestQgsProcessing; }; #endif // QGSPROCESSINGMODELALGORITHM_H diff --git a/src/core/qgsxmlutils.cpp b/src/core/qgsxmlutils.cpp index 67e4a5b108a..102ec818578 100644 --- a/src/core/qgsxmlutils.cpp +++ b/src/core/qgsxmlutils.cpp @@ -132,6 +132,19 @@ QDomElement QgsXmlUtils::writeVariant( const QVariant &value, QDomDocument &doc break; } + case QVariant::StringList: + { + QStringList list = value.toStringList(); + + Q_FOREACH ( const QString &value, list ) + { + QDomElement valueElement = writeVariant( value, doc ); + element.appendChild( valueElement ); + element.setAttribute( QStringLiteral( "type" ), QStringLiteral( "StringList" ) ); + } + break; + } + case QVariant::Int: case QVariant::Bool: case QVariant::Double: @@ -193,6 +206,17 @@ QVariant QgsXmlUtils::readVariant( const QDomElement &element ) } return list; } + else if ( type == QLatin1String( "StringList" ) ) + { + QStringList list; + QDomNodeList values = element.childNodes(); + for ( int i = 0; i < values.count(); ++i ) + { + QDomElement elem = values.at( i ).toElement(); + list.append( readVariant( elem ).toString() ); + } + return list; + } else { return QVariant(); diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 26c7bb35112..df2413f6208 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -32,6 +32,7 @@ #include "qgsgeometry.h" #include "qgsvectorfilewriter.h" #include "qgsexpressioncontext.h" +#include "qgsxmlutils.h" class DummyAlgorithm : public QgsProcessingAlgorithm { @@ -3065,6 +3066,11 @@ void TestQgsProcessing::modelerAlgorithm() QCOMPARE( alg.name(), QStringLiteral( "test" ) ); QCOMPARE( alg.displayName(), QStringLiteral( "test" ) ); QCOMPARE( alg.group(), QStringLiteral( "testGroup" ) ); + alg.setName( QStringLiteral( "test2" ) ); + QCOMPARE( alg.name(), QStringLiteral( "test2" ) ); + QCOMPARE( alg.displayName(), QStringLiteral( "test2" ) ); + alg.setGroup( QStringLiteral( "group2" ) ); + QCOMPARE( alg.group(), QStringLiteral( "group2" ) ); // child algorithms QMap algs; @@ -3299,6 +3305,90 @@ void TestQgsProcessing::modelerAlgorithm() c10.addParameterSource( "y", QgsProcessingModelAlgorithm::ChildParameterSource::fromModelParameter( "p1" ) ); alg4.setChildAlgorithm( c10 ); QVERIFY( alg4.childAlgorithmsDependOnParameter( "p1" ) ); + + + + // to/from XML + QgsProcessingModelAlgorithm alg5( "test", "testGroup" ); + QgsProcessingModelAlgorithm::ChildAlgorithm alg5c1; + alg5c1.setChildId( "cx1" ); + alg5c1.setAlgorithmId( "buffer" ); + alg5c1.addParameterSource( "x", QgsProcessingModelAlgorithm::ChildParameterSource::fromModelParameter( "p1" ) ); + alg5c1.addParameterSource( "y", QgsProcessingModelAlgorithm::ChildParameterSource::fromChildOutput( "cx2", "out3" ) ); + alg5c1.addParameterSource( "z", QgsProcessingModelAlgorithm::ChildParameterSource::fromStaticValue( 5 ) ); + alg5c1.setActive( true ); + alg5c1.setOutputsCollapsed( true ); + alg5c1.setParametersCollapsed( true ); + alg5c1.setDescription( "child 1" ); + alg5c1.setPosition( QPointF( 1, 2 ) ); + QMap alg5c1outputs; + QgsProcessingModelAlgorithm::ModelOutput alg5c1out1; + alg5c1out1.setDescription( QStringLiteral( "my output" ) ); + alg5c1out1.setPosition( QPointF( 3, 4 ) ); + alg5c1outputs.insert( QStringLiteral( "a" ), alg5c1out1 ); + alg5c1.setModelOutputs( alg5c1outputs ); + alg5.addChildAlgorithm( alg5c1 ); + + QgsProcessingModelAlgorithm::ChildAlgorithm alg5c2; + alg5c2.setChildId( "cx2" ); + alg5c2.setActive( false ); + alg5c2.setOutputsCollapsed( false ); + alg5c2.setParametersCollapsed( false ); + alg5c2.setDependencies( QStringList() << "a" << "b" ); + alg5.addChildAlgorithm( alg5c2 ); + + QMap alg5pComponents; + QgsProcessingModelAlgorithm::ModelParameter alg5pc1; + alg5pc1.setParameterName( QStringLiteral( "my_param" ) ); + alg5pc1.setPosition( QPointF( 11, 12 ) ); + alg5pComponents.insert( QStringLiteral( "my_param" ), alg5pc1 ); + alg5.setParameterComponents( alg5pComponents ); + + QDomDocument doc = QDomDocument( "model" ); + QDomElement elem = QgsXmlUtils::writeVariant( alg5.toVariant(), doc ); + doc.appendChild( elem ); + + QgsProcessingModelAlgorithm alg6; + QVERIFY( alg6.loadVariant( QgsXmlUtils::readVariant( doc.firstChildElement() ) ) ); + QCOMPARE( alg6.name(), QStringLiteral( "test" ) ); + QCOMPARE( alg6.group(), QStringLiteral( "testGroup" ) ); + QgsProcessingModelAlgorithm::ChildAlgorithm alg6c1 = alg6.childAlgorithm( "cx1" ); + QCOMPARE( alg6c1.childId(), QStringLiteral( "cx1" ) ); + QCOMPARE( alg6c1.algorithmId(), QStringLiteral( "buffer" ) ); + QVERIFY( alg6c1.isActive() ); + QVERIFY( alg6c1.outputsCollapsed() ); + QVERIFY( alg6c1.parametersCollapsed() ); + QCOMPARE( alg6c1.description(), QStringLiteral( "child 1" ) ); + QCOMPARE( alg6c1.position().x(), 1.0 ); + QCOMPARE( alg6c1.position().y(), 2.0 ); + QCOMPARE( alg6c1.parameterSources().count(), 3 ); + QCOMPARE( alg6c1.parameterSources().value( "x" ).source(), QgsProcessingModelAlgorithm::ChildParameterSource::ModelParameter ); + QCOMPARE( alg6c1.parameterSources().value( "x" ).parameterName(), QStringLiteral( "p1" ) ); + QCOMPARE( alg6c1.parameterSources().value( "y" ).source(), QgsProcessingModelAlgorithm::ChildParameterSource::ChildOutput ); + QCOMPARE( alg6c1.parameterSources().value( "y" ).outputChildId(), QStringLiteral( "cx2" ) ); + QCOMPARE( alg6c1.parameterSources().value( "y" ).outputName(), QStringLiteral( "out3" ) ); + QCOMPARE( alg6c1.parameterSources().value( "z" ).source(), QgsProcessingModelAlgorithm::ChildParameterSource::StaticValue ); + QCOMPARE( alg6c1.parameterSources().value( "z" ).staticValue().toInt(), 5 ); + QCOMPARE( alg6c1.modelOutputs().count(), 1 ); + QCOMPARE( alg6c1.modelOutputs().value( QStringLiteral( "a" ) ).description(), QStringLiteral( "my output" ) ); + QCOMPARE( alg6c1.modelOutput( "a" ).description(), QStringLiteral( "my output" ) ); + QCOMPARE( alg6c1.modelOutput( "a" ).position().x(), 3.0 ); + QCOMPARE( alg6c1.modelOutput( "a" ).position().y(), 4.0 ); + + + QgsProcessingModelAlgorithm::ChildAlgorithm alg6c2 = alg6.childAlgorithm( "cx2" ); + QCOMPARE( alg6c2.childId(), QStringLiteral( "cx2" ) ); + QVERIFY( !alg6c2.isActive() ); + QVERIFY( !alg6c2.outputsCollapsed() ); + QVERIFY( !alg6c2.parametersCollapsed() ); + QCOMPARE( alg6c2.dependencies(), QStringList() << "a" << "b" ); + + QCOMPARE( alg6.parameterComponents().count(), 1 ); + QCOMPARE( alg6.parameterComponents().value( QStringLiteral( "my_param" ) ).parameterName(), QStringLiteral( "my_param" ) ); + QCOMPARE( alg6.parameterComponent( "my_param" ).parameterName(), QStringLiteral( "my_param" ) ); + QCOMPARE( alg6.parameterComponent( "my_param" ).position().x(), 11.0 ); + QCOMPARE( alg6.parameterComponent( "my_param" ).position().y(), 12.0 ); + } QGSTEST_MAIN( TestQgsProcessing )