Skip to content

Commit 6efa765

Browse files
authoredJun 24, 2017
Merge pull request #4770 from nyalldawson/proc
More processing model porting, better invalid geometry feedback
2 parents b7e66b4 + d667bf5 commit 6efa765

19 files changed

+262
-73
lines changed
 

‎python/core/processing/qgsprocessingcontext.sip

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,6 @@ Destination project
142142
.. seealso:: invalidGeometryCheck()
143143
%End
144144

145-
146145
void setInvalidGeometryCallback( SIP_PYCALLABLE / AllowNone / );
147146
%Docstring
148147
Sets a callback function to use when encountering an invalid geometry and
@@ -198,16 +197,29 @@ Destination project
198197
%Docstring
199198
Sets the default ``encoding`` to use for newly created files.
200199
.. seealso:: defaultEncoding()
200+
%End
201+
202+
QgsProcessingFeedback *feedback();
203+
%Docstring
204+
Returns the associated feedback object.
205+
.. seealso:: setFeedback()
206+
:rtype: QgsProcessingFeedback
207+
%End
208+
209+
void setFeedback( QgsProcessingFeedback *feedback );
210+
%Docstring
211+
Sets an associated ``feedback`` object. This allows context related functions
212+
to report feedback and errors to users and processing logs. While ideally this feedback
213+
object should outlive the context, only a weak pointer to ``feedback`` is stored
214+
and no errors will occur if feedback is deleted before the context.
215+
Ownership of ``feedback`` is not transferred.
216+
.. seealso:: setFeedback()
201217
%End
202218

203219
private:
204220
QgsProcessingContext( const QgsProcessingContext &other );
205221
};
206222

207-
208-
209-
210-
211223
QFlags<QgsProcessingContext::Flag> operator|(QgsProcessingContext::Flag f1, QFlags<QgsProcessingContext::Flag> f2);
212224

213225

‎python/core/processing/qgsprocessingmodelalgorithm.sip

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -566,9 +566,15 @@ Copies are protected to avoid slicing
566566

567567
virtual QString svgIconPath() const;
568568

569+
virtual QString shortHelpString() const;
570+
571+
virtual QString helpUrl() const;
572+
569573

570574
virtual bool canExecute( QString *errorMessage /Out/ = 0 ) const;
571575

576+
virtual QString asPythonCommand( const QVariantMap &parameters, QgsProcessingContext &context ) const;
577+
572578

573579
void setName( const QString &name );
574580
%Docstring
@@ -767,6 +773,35 @@ Copies are protected to avoid slicing
767773
:rtype: bool
768774
%End
769775

776+
QVariantMap &helpContent();
777+
%Docstring
778+
Returns the model's help contents (a free-form map of values describing the algorithm's
779+
use and metadata).
780+
.. seealso:: setHelpContent()
781+
:rtype: QVariantMap
782+
%End
783+
784+
785+
void setHelpContent( const QVariantMap &contents );
786+
%Docstring
787+
Sets the model's help ``contents`` (a free-form map of values describing the algorithm's
788+
use and metadata).
789+
.. seealso:: helpContent()
790+
%End
791+
792+
QString sourceFilePath() const;
793+
%Docstring
794+
Returns the source file path for the model, if available.
795+
.. seealso:: setSourceFilePath()
796+
:rtype: str
797+
%End
798+
799+
void setSourceFilePath( const QString &path );
800+
%Docstring
801+
Sets the source file ``path`` for the model, if available.
802+
.. seealso:: sourceFilePath()
803+
%End
804+
770805
protected:
771806

772807
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,

‎python/core/processing/qgsprocessingutils.sip

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,13 @@ class QgsProcessingUtils
151151
:rtype: str
152152
%End
153153

154+
static QString formatHelpMapAsHtml( const QVariantMap &map, const QgsProcessingAlgorithm *algorithm );
155+
%Docstring
156+
Returns a HTML formatted version of the help text encoded in a variant ``map`` for
157+
a specified ``algorithm``.
158+
:rtype: str
159+
%End
160+
154161
};
155162

156163

‎python/plugins/processing/algs/qgis/AutoincrementalField.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def processAlgorithm(self, parameters, context, feedback):
7575
fields, source.wkbType(), source.sourceCrs())
7676

7777
features = source.getFeatures()
78-
total = total = 100.0 / source.featureCount() if source.featureCount() else 0
78+
total = 100.0 / source.featureCount() if source.featureCount() else 0
7979
for current, input_feature in enumerate(features):
8080
if feedback.isCanceled():
8181
break

‎python/plugins/processing/core/GeoAlgorithm.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,3 @@ def executeAlgorithm(alg, parameters, context=None, feedback=None, model=None):
341341
return result, ok
342342
#self.convertUnsupportedFormats(context, feedback)
343343
#self.runPostExecutionScript(feedback)
344-
345-
def helpUrl(self):
346-
return QgsHelp.helpUrl("processing_algs/{}/{}".format(
347-
self.provider().id(), self.id())).toString()

‎python/plugins/processing/gui/AlgorithmDialogBase.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
from qgis.core import (QgsProject,
3939
QgsProcessingFeedback,
4040
QgsSettings)
41-
41+
from qgis.gui import QgsHelp
4242

4343
from processing.core.ProcessingConfig import ProcessingConfig
4444

@@ -189,7 +189,7 @@ def resetGUI(self):
189189

190190
def setInfo(self, msg, error=False, escape_html=True):
191191
if error:
192-
self.txtLog.append('<span style="color:red"><br>{}<br></span>'.format(msg, quote=False))
192+
self.txtLog.append('<span style="color:red">{}</span><br />'.format(msg, quote=False))
193193
elif escape_html:
194194
self.txtLog.append(html.escape(msg))
195195
else:
@@ -255,6 +255,10 @@ def splitterChanged(self, pos, index):
255255

256256
def openHelp(self):
257257
algHelp = self.alg.helpUrl()
258+
if not algHelp:
259+
algHelp = QgsHelp.helpUrl("processing_algs/{}/{}".format(
260+
self.alg.provider().id(), self.alg.id())).toString()
261+
258262
if algHelp not in [None, ""]:
259263
webbrowser.open(algHelp)
260264

‎python/plugins/processing/gui/HelpEditionDialog.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
from qgis.PyQt.QtWidgets import QDialog, QTreeWidgetItem
3434

3535
from qgis.core import (QgsMessageLog,
36-
QgsProcessingUtils)
36+
QgsProcessingUtils,
37+
QgsProcessingParameterDefinition)
3738
from processing.modeler.ModelerAlgorithm import ModelerAlgorithm
3839

3940
pluginPath = os.path.split(os.path.dirname(__file__))[0]
@@ -55,7 +56,7 @@ def __init__(self, alg):
5556
self.alg = alg
5657
self.descriptions = {}
5758
if isinstance(self.alg, ModelerAlgorithm):
58-
self.descriptions = self.alg.helpContent
59+
self.descriptions = self.alg.helpContent()
5960
else:
6061
if self.alg.descriptionFile is not None:
6162
helpfile = alg.descriptionFile + '.help'
@@ -90,7 +91,7 @@ def getHtml(self):
9091
s += '<h3>' + param.description() + '</h3>\n'
9192
s += '<p>' + self.getDescription(param.name()) + '</p>\n'
9293
s += self.tr('<h2>Outputs</h2>\n')
93-
for out in self.alg.outputs:
94+
for out in self.alg.outputDefinitions():
9495
s += '<h3>' + out.description() + '</h3>\n'
9596
s += '<p>' + self.getDescription(out.name()) + '</p>\n'
9697
return s
@@ -101,11 +102,14 @@ def fillTree(self):
101102
parametersItem = TreeDescriptionItem(self.tr('Input parameters'), None)
102103
self.tree.addTopLevelItem(parametersItem)
103104
for param in self.alg.parameterDefinitions():
105+
if param.flags() & QgsProcessingParameterDefinition.FlagHidden or param.isDestination():
106+
continue
107+
104108
item = TreeDescriptionItem(param.description(), param.name())
105109
parametersItem.addChild(item)
106110
outputsItem = TreeDescriptionItem(self.tr('Outputs'), None)
107111
self.tree.addTopLevelItem(outputsItem)
108-
for out in self.alg.outputs:
112+
for out in self.alg.outputDefinitions():
109113
item = TreeDescriptionItem(out.description(), out.name())
110114
outputsItem.addChild(item)
111115
item = TreeDescriptionItem(self.tr('Algorithm created by'), self.ALG_CREATOR)

‎python/plugins/processing/modeler/ModelerAlgorithm.py

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -174,14 +174,9 @@ def asPythonParameter(self):
174174

175175
class ModelerAlgorithm(QgsProcessingModelAlgorithm):
176176

177-
CANVAS_SIZE = 4000
178-
179177
def __init__(self):
180178
super().__init__()
181179

182-
self.descriptionFile = None
183-
self.helpContent = {}
184-
185180
# Geoalgorithms in this model. A dict of Algorithm objects, with names as keys
186181
self.algs = {}
187182

@@ -229,23 +224,6 @@ def resolveValue(self, value, param):
229224
v = value
230225
return param.evaluateForModeler(v, self)
231226

232-
def asPythonCommand(self, parameters, context):
233-
if self.descriptionFile:
234-
return QgsProcessingAlgorithm.asPythonCommand(self, parameters, context)
235-
else:
236-
return None
237-
238-
def helpUrl(self):
239-
try:
240-
return getHtmlFromDescriptionsDict(self, self.helpContent)
241-
except:
242-
return None
243-
244-
def shortHelpString(self):
245-
if 'ALG_DESC' in self.helpContent:
246-
return str(self.helpContent['ALG_DESC'])
247-
return None
248-
249227
def toPython(self):
250228
s = ['##%s=name' % self.name()]
251229
for param in list(self.parameterComponents().values()):

‎python/plugins/processing/modeler/ModelerAlgorithmProvider.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def loadFromFolder(self, folder):
108108
alg = ModelerAlgorithm()
109109
if alg.fromFile(fullpath):
110110
if alg.name():
111-
alg.descriptionFile = fullpath
111+
alg.setSourceFilePath(fullpath)
112112
self.algs.append(alg)
113113
else:
114114
QgsMessageLog.logMessage(self.tr('Could not load model {0}', 'ModelerAlgorithmProvider').format(descriptionFile),

‎python/plugins/processing/modeler/ModelerDialog.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ def editHelp(self):
279279
dlg = HelpEditionDialog(alg)
280280
dlg.exec_()
281281
if dlg.descriptions:
282-
self.model.helpContent = dlg.descriptions
282+
self.model.setHelpContent(dlg.descriptions)
283283
self.hasChanged = True
284284

285285
def runModel(self):
@@ -439,8 +439,8 @@ def saveModel(self, saveAs):
439439
return
440440
self.model.setName(str(self.textName.text()))
441441
self.model.setGroup(str(self.textGroup.text()))
442-
if self.model.descriptionFile is not None and not saveAs:
443-
filename = self.model.descriptionFile
442+
if self.model.sourceFilePath() is not None and not saveAs:
443+
filename = self.model.sourceFilePath()
444444
else:
445445
filename, filter = QFileDialog.getSaveFileName(self,
446446
self.tr('Save Model'),
@@ -449,7 +449,7 @@ def saveModel(self, saveAs):
449449
if filename:
450450
if not filename.endswith('.model3'):
451451
filename += '.model3'
452-
self.model.descriptionFile = filename
452+
self.model.setSourceFilePath(filename)
453453
if filename:
454454
if not self.model.toFile(filename):
455455
if saveAs:
@@ -492,8 +492,8 @@ def openModel(self):
492492

493493
def repaintModel(self, controls=True):
494494
self.scene = ModelerScene(self, dialog=self)
495-
self.scene.setSceneRect(QRectF(0, 0, ModelerAlgorithm.CANVAS_SIZE,
496-
ModelerAlgorithm.CANVAS_SIZE))
495+
self.scene.setSceneRect(QRectF(0, 0, self.CANVAS_SIZE,
496+
self.CANVAS_SIZE))
497497
self.scene.paintModel(self.model, controls)
498498
self.view.setScene(self.scene)
499499

@@ -512,6 +512,7 @@ def addInputOfType(self, paramType, pos=None):
512512
if isinstance(pos, QPoint):
513513
pos = QPointF(pos)
514514
component = QgsProcessingModelAlgorithm.ModelParameter(dlg.param.name())
515+
component.setDescription(dlg.param.name())
515516
component.setPosition(pos)
516517
self.model.addModelParameter(dlg.param, component)
517518
self.repaintModel()

‎python/plugins/processing/modeler/ModelerGraphicItem.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ def editElement(self):
196196
if dlg.param is not None:
197197
self.model.removeModelParameter(self.element.parameterName())
198198
self.element.setParameterName(dlg.param.name())
199+
self.element.setDescription(dlg.param.name())
199200
self.model.addModelParameter(dlg.param, self.element)
200201
self.text = dlg.param.description()
201202
self.update()

‎python/plugins/processing/tests/QgisAlgorithmsTest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,7 @@ def displayName(self):
4949
return 'testalg'
5050

5151
def processAlgorithm(self, parameters, context, feedback):
52-
raise GeoAlgorithmExecutionException(
53-
self.tr('Exception while processing'))
52+
raise GeoAlgorithmExecutionException('Exception while processing')
5453
return {}
5554

5655

‎python/plugins/processing/tools/dataobjects.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -71,27 +71,13 @@ def createContext(feedback=None):
7171
"""
7272
context = QgsProcessingContext()
7373
context.setProject(QgsProject.instance())
74+
context.setFeedback(feedback)
7475

7576
invalid_features_method = ProcessingConfig.getSetting(ProcessingConfig.FILTER_INVALID_GEOMETRIES)
7677
if invalid_features_method is None:
7778
invalid_features_method = QgsFeatureRequest.GeometryAbortOnInvalid
7879
context.setInvalidGeometryCheck(invalid_features_method)
7980

80-
def raise_invalid_geometry_error(f, feedback=feedback):
81-
if feedback:
82-
feedback.pushInfo(QCoreApplication.translate("FeatureIterator",
83-
'Feature with id {} has invalid geometry, skipping feature.'.format(f.id())))
84-
85-
if context.invalidGeometryCheck() == QgsFeatureRequest.GeometrySkipInvalid:
86-
context.setInvalidGeometryCallback(raise_invalid_geometry_error)
87-
88-
def raise_transform_error(f, feedback=feedback):
89-
if feedback:
90-
feedback.pushInfo(QCoreApplication.translate("FeatureIterator",
91-
'Encountered a transform error when reprojecting feature with id {}.'.format(f.id())))
92-
93-
context.setTransformErrorCallback(raise_transform_error)
94-
9581
settings = QgsSettings()
9682
context.setDefaultEncoding(settings.value("/Processing/encoding", "System"))
9783

‎src/core/processing/qgsprocessingcontext.h

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "qgsfeaturerequest.h"
2626
#include "qgsmaplayerlistutils.h"
2727
#include "qgsexception.h"
28+
#include "qgsprocessingfeedback.h"
2829

2930
/**
3031
* \class QgsProcessingContext
@@ -50,7 +51,15 @@ class CORE_EXPORT QgsProcessingContext
5051
/**
5152
* Constructor for QgsProcessingContext.
5253
*/
53-
QgsProcessingContext() = default;
54+
QgsProcessingContext()
55+
{
56+
auto callback = [ = ]( const QgsFeature & feature )
57+
{
58+
if ( mFeedback )
59+
mFeedback->reportError( QObject::tr( "Encountered a transform error when reprojecting feature with id %1." ).arg( feature.id() ) );
60+
};
61+
mTransformErrorCallback = callback;
62+
}
5463

5564
//! QgsProcessingContext cannot be copied
5665
QgsProcessingContext( const QgsProcessingContext &other ) = delete;
@@ -176,17 +185,34 @@ class CORE_EXPORT QgsProcessingContext
176185
{
177186
mInvalidGeometryCheck = check;
178187

179-
if ( mInvalidGeometryCheck == QgsFeatureRequest::GeometryAbortOnInvalid )
188+
switch ( mInvalidGeometryCheck )
180189
{
181-
auto callback = []( const QgsFeature & feature )
190+
case QgsFeatureRequest::GeometryAbortOnInvalid:
191+
{
192+
auto callback = []( const QgsFeature & feature )
193+
{
194+
throw QgsProcessingException( QObject::tr( "Feature (%1) has invalid geometry. Please fix the geometry or change the Processing setting to the \"Ignore invalid input features\" option." ).arg( feature.id() ) );
195+
};
196+
mInvalidGeometryCallback = callback;
197+
break;
198+
}
199+
200+
case QgsFeatureRequest::GeometrySkipInvalid:
182201
{
183-
throw QgsProcessingException( QObject::tr( "Feature (%1) has invalid geometry. Please fix the geometry or change the Processing setting to the \"Ignore invalid input features\" option." ).arg( feature.id() ) );
184-
};
185-
mInvalidGeometryCallback = callback;
202+
auto callback = [ = ]( const QgsFeature & feature )
203+
{
204+
if ( mFeedback )
205+
mFeedback->reportError( QObject::tr( "Feature (%1) has invalid geometry and has been skipped. Please fix the geometry or change the Processing setting to the \"Ignore invalid input features\" option." ).arg( feature.id() ) );
206+
};
207+
mInvalidGeometryCallback = callback;
208+
break;
209+
}
210+
211+
default:
212+
break;
186213
}
187214
}
188215

189-
190216
/**
191217
* Sets a callback function to use when encountering an invalid geometry and
192218
* invalidGeometryCheck() is set to GeometryAbortOnInvalid. This function will be
@@ -268,6 +294,22 @@ class CORE_EXPORT QgsProcessingContext
268294
*/
269295
void setDefaultEncoding( const QString &encoding ) { mDefaultEncoding = encoding; }
270296

297+
/**
298+
* Returns the associated feedback object.
299+
* \see setFeedback()
300+
*/
301+
QgsProcessingFeedback *feedback() { return mFeedback; }
302+
303+
/**
304+
* Sets an associated \a feedback object. This allows context related functions
305+
* to report feedback and errors to users and processing logs. While ideally this feedback
306+
* object should outlive the context, only a weak pointer to \a feedback is stored
307+
* and no errors will occur if feedback is deleted before the context.
308+
* Ownership of \a feedback is not transferred.
309+
* \see setFeedback()
310+
*/
311+
void setFeedback( QgsProcessingFeedback *feedback ) { mFeedback = feedback; }
312+
271313
private:
272314

273315
QgsProcessingContext::Flags mFlags = 0;
@@ -282,15 +324,13 @@ class CORE_EXPORT QgsProcessingContext
282324
QString mDefaultEncoding;
283325
QMap< QString, LayerDetails > mLayersToLoadOnCompletion;
284326

327+
QPointer< QgsProcessingFeedback > mFeedback;
328+
285329
#ifdef SIP_RUN
286330
QgsProcessingContext( const QgsProcessingContext &other );
287331
#endif
288332
};
289333

290-
291-
292-
293-
294334
Q_DECLARE_OPERATORS_FOR_FLAGS( QgsProcessingContext::Flags )
295335

296336
#endif // QGSPROCESSINGPARAMETERS_H

‎src/core/processing/qgsprocessingmodelalgorithm.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "qgsprocessingmodelalgorithm.h"
1919
#include "qgsprocessingregistry.h"
2020
#include "qgsprocessingfeedback.h"
21+
#include "qgsprocessingutils.h"
2122
#include "qgsxmlutils.h"
2223
#include "qgsexception.h"
2324
#include <QFile>
@@ -292,6 +293,18 @@ QString QgsProcessingModelAlgorithm::svgIconPath() const
292293
return QgsApplication::iconPath( QStringLiteral( "processingModel.svg" ) );
293294
}
294295

296+
QString QgsProcessingModelAlgorithm::shortHelpString() const
297+
{
298+
if ( mHelpContent.contains( QStringLiteral( "ALG_DESC" ) ) )
299+
return mHelpContent.value( QStringLiteral( "ALG_DESC" ) ).toString();
300+
return QString();
301+
}
302+
303+
QString QgsProcessingModelAlgorithm::helpUrl() const
304+
{
305+
return QgsProcessingUtils::formatHelpMapAsHtml( mHelpContent, this );
306+
}
307+
295308
QVariantMap QgsProcessingModelAlgorithm::parametersForChildAlgorithm( const ChildAlgorithm &child, const QVariantMap &modelParameters, const QMap< QString, QVariantMap > &results ) const
296309
{
297310
QVariantMap childParams;
@@ -468,6 +481,26 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa
468481
return finalResults;
469482
}
470483

484+
QString QgsProcessingModelAlgorithm::sourceFilePath() const
485+
{
486+
return mSourceFile;
487+
}
488+
489+
void QgsProcessingModelAlgorithm::setSourceFilePath( const QString &sourceFile )
490+
{
491+
mSourceFile = sourceFile;
492+
}
493+
494+
QVariantMap QgsProcessingModelAlgorithm::helpContent() const
495+
{
496+
return mHelpContent;
497+
}
498+
499+
void QgsProcessingModelAlgorithm::setHelpContent( const QVariantMap &helpContent )
500+
{
501+
mHelpContent = helpContent;
502+
}
503+
471504
void QgsProcessingModelAlgorithm::setName( const QString &name )
472505
{
473506
mModelName = name;
@@ -559,6 +592,7 @@ QVariant QgsProcessingModelAlgorithm::toVariant() const
559592
QVariantMap map;
560593
map.insert( QStringLiteral( "model_name" ), mModelName );
561594
map.insert( QStringLiteral( "model_group" ), mModelGroup );
595+
map.insert( QStringLiteral( "help" ), mHelpContent );
562596

563597
QVariantMap childMap;
564598
QMap< QString, ChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin();
@@ -592,6 +626,7 @@ bool QgsProcessingModelAlgorithm::loadVariant( const QVariant &model )
592626

593627
mModelName = map.value( QStringLiteral( "model_name" ) ).toString();
594628
mModelGroup = map.value( QStringLiteral( "model_group" ) ).toString();
629+
mHelpContent = map.value( QStringLiteral( "help" ) ).toMap();
595630

596631
mChildAlgorithms.clear();
597632
QVariantMap childMap = map.value( QStringLiteral( "children" ) ).toMap();
@@ -880,6 +915,14 @@ bool QgsProcessingModelAlgorithm::canExecute( QString *errorMessage ) const
880915
return true;
881916
}
882917

918+
QString QgsProcessingModelAlgorithm::asPythonCommand( const QVariantMap &parameters, QgsProcessingContext &context ) const
919+
{
920+
if ( mSourceFile.isEmpty() )
921+
return QString(); // temporary model - can't run as python command
922+
923+
return QgsProcessingAlgorithm::asPythonCommand( parameters, context );
924+
}
925+
883926

884927
bool QgsProcessingModelAlgorithm::ChildParameterSource::operator==( const QgsProcessingModelAlgorithm::ChildParameterSource &other ) const
885928
{

‎src/core/processing/qgsprocessingmodelalgorithm.h

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,8 +569,11 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
569569
QString group() const override;
570570
QIcon icon() const override;
571571
QString svgIconPath() const override;
572+
QString shortHelpString() const override;
573+
QString helpUrl() const override;
572574

573575
bool canExecute( QString *errorMessage SIP_OUT = nullptr ) const override;
576+
QString asPythonCommand( const QVariantMap &parameters, QgsProcessingContext &context ) const override;
574577

575578
/**
576579
* Sets the model \a name.
@@ -757,6 +760,39 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
757760
*/
758761
bool fromFile( const QString &path );
759762

763+
/**
764+
* Returns the model's help contents (a free-form map of values describing the algorithm's
765+
* use and metadata).
766+
* \see setHelpContent()
767+
*/
768+
QVariantMap &helpContent() { return mHelpContent; }
769+
770+
/**
771+
* Returns the model's help contents (a free-form map of values describing the algorithm's
772+
* use and metadata).
773+
* \see setHelpContent()
774+
*/
775+
SIP_SKIP QVariantMap helpContent() const;
776+
777+
/**
778+
* Sets the model's help \a contents (a free-form map of values describing the algorithm's
779+
* use and metadata).
780+
* \see helpContent()
781+
*/
782+
void setHelpContent( const QVariantMap &contents );
783+
784+
/**
785+
* Returns the source file path for the model, if available.
786+
* \see setSourceFilePath()
787+
*/
788+
QString sourceFilePath() const;
789+
790+
/**
791+
* Sets the source file \a path for the model, if available.
792+
* \see sourceFilePath()
793+
*/
794+
void setSourceFilePath( const QString &path );
795+
760796
protected:
761797

762798
QVariantMap processAlgorithm( const QVariantMap &parameters,
@@ -772,6 +808,11 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm
772808
//! Map of parameter name to model parameter component
773809
QMap< QString, ModelParameter > mParameterComponents;
774810

811+
QVariantMap mHelpContent;
812+
813+
//! Model source file
814+
QString mSourceFile;
815+
775816
void dependsOnChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends ) const;
776817
void dependentChildAlgorithmsRecursive( const QString &childId, QSet<QString> &depends ) const;
777818

‎src/core/processing/qgsprocessingutils.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "qgsvectorfilewriter.h"
2525
#include "qgsmemoryproviderutils.h"
2626
#include "qgsprocessingparameters.h"
27+
#include "qgsprocessingalgorithm.h"
2728

2829
QList<QgsRasterLayer *> QgsProcessingUtils::compatibleRasterLayers( QgsProject *project, bool sort )
2930
{
@@ -428,6 +429,38 @@ QString QgsProcessingUtils::generateTempFilename( const QString &basename )
428429
return path + '/' + basename;
429430
}
430431

432+
QString QgsProcessingUtils::formatHelpMapAsHtml( const QVariantMap &map, const QgsProcessingAlgorithm *algorithm )
433+
{
434+
auto getText = [map]( const QString & key )->QString
435+
{
436+
if ( map.contains( key ) )
437+
return map.value( key ).toString();
438+
return QString();
439+
};
440+
441+
QString s = QObject::tr( "<html><body><h2>Algorithm description</h2>\n " );
442+
s += QStringLiteral( "<p>" ) + getText( QStringLiteral( "ALG_DESC" ) ) + QStringLiteral( "</p>\n" );
443+
s += QObject::tr( "<h2>Input parameters</h2>\n" );
444+
445+
Q_FOREACH ( const QgsProcessingParameterDefinition *def, algorithm->parameterDefinitions() )
446+
{
447+
s += QStringLiteral( "<h3>" ) + def->description() + QStringLiteral( "</h3>\n" );
448+
s += QStringLiteral( "<p>" ) + getText( def->name() ) + QStringLiteral( "</p>\n" );
449+
}
450+
s += QObject::tr( "<h2>Outputs</h2>\n" );
451+
Q_FOREACH ( const QgsProcessingOutputDefinition *def, algorithm->outputDefinitions() )
452+
{
453+
s += QStringLiteral( "<h3>" ) + def->description() + QStringLiteral( "</h3>\n" );
454+
s += QStringLiteral( "<p>" ) + getText( def->name() ) + QStringLiteral( "</p>\n" );
455+
}
456+
s += "<br>";
457+
s += QObject::tr( "<p align=\"right\">Algorithm author: %1</p>" ).arg( getText( QStringLiteral( "ALG_CREATOR" ) ) );
458+
s += QObject::tr( "<p align=\"right\">Help author: %1</p>" ).arg( getText( QStringLiteral( "ALG_HELP_CREATOR" ) ) );
459+
s += QObject::tr( "<p align=\"right\">Algorithm version: %1</p>" ).arg( getText( QStringLiteral( "ALG_VERSION" ) ) );
460+
s += QStringLiteral( "</body></html>" );
461+
return s;
462+
}
463+
431464

432465
//
433466
// QgsProcessingFeatureSource

‎src/core/processing/qgsprocessingutils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,12 @@ class CORE_EXPORT QgsProcessingUtils
183183
*/
184184
static QString generateTempFilename( const QString &basename );
185185

186+
/**
187+
* Returns a HTML formatted version of the help text encoded in a variant \a map for
188+
* a specified \a algorithm.
189+
*/
190+
static QString formatHelpMapAsHtml( const QVariantMap &map, const QgsProcessingAlgorithm *algorithm );
191+
186192
private:
187193

188194
static bool canUseLayer( const QgsRasterLayer *layer );

‎tests/src/core/testqgsprocessing.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3732,6 +3732,8 @@ void TestQgsProcessing::modelerAlgorithm()
37323732

37333733
// to/from XML
37343734
QgsProcessingModelAlgorithm alg5( "test", "testGroup" );
3735+
alg5.helpContent().insert( "author", "me" );
3736+
alg5.helpContent().insert( "usage", "run" );
37353737
QgsProcessingModelAlgorithm::ChildAlgorithm alg5c1;
37363738
alg5c1.setChildId( "cx1" );
37373739
alg5c1.setAlgorithmId( "buffer" );
@@ -3772,6 +3774,7 @@ void TestQgsProcessing::modelerAlgorithm()
37723774
QVERIFY( alg6.loadVariant( QgsXmlUtils::readVariant( doc.firstChildElement() ) ) );
37733775
QCOMPARE( alg6.name(), QStringLiteral( "test" ) );
37743776
QCOMPARE( alg6.group(), QStringLiteral( "testGroup" ) );
3777+
QCOMPARE( alg6.helpContent(), alg5.helpContent() );
37753778
QgsProcessingModelAlgorithm::ChildAlgorithm alg6c1 = alg6.childAlgorithm( "cx1" );
37763779
QCOMPARE( alg6c1.childId(), QStringLiteral( "cx1" ) );
37773780
QCOMPARE( alg6c1.algorithmId(), QStringLiteral( "buffer" ) );

0 commit comments

Comments
 (0)
Please sign in to comment.