Skip to content

Commit ddd522b

Browse files
committed
Rework validity check API to allow future background threaded use
1 parent ec55304 commit ddd522b

14 files changed

+181
-45
lines changed

python/core/auto_generated/validity/qgsabstractvaliditycheck.sip.in

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Checks can be used for many different use cases, e.g. validating a layout's cont
5454
an export to occur, or validating the contents of a Processing model (and warning if required plugin based
5555
providers are not available or if compulsory algorithm parameters have not been populated).
5656

57-
Subclasses must indicate the type of check the represent via the checkType() method. When checks are performed,
57+
Subclasses must indicate the type of check they represent via the checkType() method. When checks are performed,
5858
all the registered checks with a matching check type are performed sequentially. The check type also
5959
dictates the subclass of the QgsValidityCheckContext which is given to the subclass' runCheck method.
6060

@@ -77,26 +77,50 @@ Checks must be registered in the application's :py:class:`QgsValidityCheckRegist
7777
TypeUserCheck,
7878
};
7979

80+
virtual QgsAbstractValidityCheck *create() const = 0 /Factory/;
81+
%Docstring
82+
Creates a new instance of the check and returns it.
83+
%End
84+
8085
virtual QString id() const = 0;
8186
%Docstring
8287
Returns the unique ID of the check.
88+
89+
This is a non-translated, non-user visible string identifying the check.
8390
%End
8491

8592
virtual int checkType() const = 0;
8693
%Docstring
8794
Returns the type of the check.
8895
%End
8996

90-
virtual QString name() const = 0;
97+
virtual bool prepareCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback );
9198
%Docstring
92-
Returns the name of the check.
99+
Prepares the check for execution, and returns true if the check can be run.
100+
101+
This method is always called from the main thread, and subclasses can implement
102+
it to do preparatory steps which are not thread safe (e.g. obtaining feature
103+
sources from vector layers). It is followed by a call to runCheck(), which
104+
may be performed in a background thread.
105+
106+
Individual calls to prepareCheck()/runCheck() are run on a new instance of the
107+
check (see create()), so subclasses can safely store state from the prepareCheck() method
108+
ready for the subsequent runCheck() method.
109+
110+
The ``context`` argument gives the wider in which the check is being run.
93111
%End
94112

95-
virtual QList< QgsValidityCheckResult > runCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback ) const = 0;
113+
virtual QList< QgsValidityCheckResult > runCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback ) = 0;
96114
%Docstring
97115
Runs the check and returns a list of results. If the check is "passed" and no warnings or errors are generated,
98116
then an empty list should be returned.
99117

118+
This method may be called in a background thread, so subclasses should take care to ensure that
119+
only thread-safe methods are used. It is always preceeded by a call to prepareCheck().
120+
121+
If a check needs to perform non-thread-safe tests, these should be implemented within prepareCheck()
122+
and stored in the subclass instance to be returned by this method.
123+
100124
The ``context`` argument gives the wider in which the check is being run.
101125
%End
102126

python/core/auto_generated/validity/qgsvaliditycheckcontext.sip.in

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ context, containing a reference to the QgsLayout to be checked.
3232
%End
3333
public:
3434

35+
enum ContextType
36+
{
37+
TypeLayoutContext,
38+
TypeUserContext,
39+
};
40+
41+
virtual int type() const = 0;
42+
%Docstring
43+
Returns the context type.
44+
%End
45+
3546
virtual ~QgsValidityCheckContext();
3647

3748
};
@@ -57,6 +68,8 @@ indicate they are of the QgsAbstractValidityCheck.TypeLayoutCheck type.
5768
Constructor for QgsLayoutValidityCheckContext for the specified ``layout``.
5869
%End
5970

71+
virtual int type() const;
72+
6073
QgsLayout *layout;
6174

6275
};

python/core/auto_generated/validity/qgsvaliditycheckregistry.sip.in

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ QgsValidityCheckRegistry is not usually directly created, but rather accessed th
2929
~QgsValidityCheckRegistry();
3030

3131

32-
QList<QgsAbstractValidityCheck *> checks() const;
32+
QList<const QgsAbstractValidityCheck *> checks() const;
3333
%Docstring
3434
Returns the list of available checks.
3535
%End
3636

37-
QList<QgsAbstractValidityCheck *> checks( int type ) const;
37+
QList<const QgsAbstractValidityCheck *> checks( int type ) const;
3838
%Docstring
3939
Returns the list of all available checks of the matching ``type``.
4040
%End
@@ -62,6 +62,9 @@ The ``context`` argument gives the wider in which the check is being run.
6262

6363
The ``feedback`` argument is used to give progress reports and to support
6464
cancelation of long-running checks.
65+
66+
This is a blocking call, which will run all matching checks in the main
67+
thread and only return when they have all completed.
6568
%End
6669

6770
private:

python/gui/auto_generated/qgsvaliditycheckresultswidget.sip.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ on the dialog and proceed with the operation. The function will return false.
9393

9494
Returns true if no warnings were encountered (and no dialog was shown to users), or if only
9595
warnings were shown and the user clicked OK after being shown these warnings.
96+
97+
This method is a blocking method, and runs all checks in the main thread.
9698
%End
9799

98100
};

src/app/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,6 @@ SET (QGIS_APP_MOC_HDRS
434434
layout/qgslayoutscalebarwidget.h
435435
layout/qgslayoutshapewidget.h
436436
layout/qgslayouttablebackgroundcolorsdialog.h
437-
layout/qgslayoutvaliditychecks.h
438437
layout/qgsreportfieldgroupsectionwidget.h
439438
layout/qgsreportlayoutsectionwidget.h
440439
layout/qgsreportorganizerwidget.h

src/app/layout/qgslayoutvaliditychecks.cpp

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,23 @@
1919
#include "qgslayoutitemmap.h"
2020
#include "qgslayout.h"
2121

22-
QList<QgsValidityCheckResult> QgsLayoutMapCrsValidityCheck::runCheck( const QgsValidityCheckContext *context, QgsFeedback * ) const
22+
QgsLayoutMapCrsValidityCheck *QgsLayoutMapCrsValidityCheck::create() const
2323
{
24-
QList<QgsValidityCheckResult> results;
25-
const QgsLayoutValidityCheckContext *layoutContext = dynamic_cast< const QgsLayoutValidityCheckContext * >( context );
24+
return new QgsLayoutMapCrsValidityCheck();
25+
}
26+
27+
QString QgsLayoutMapCrsValidityCheck::id() const { return QStringLiteral( "map_crs_check" ); }
28+
29+
int QgsLayoutMapCrsValidityCheck::checkType() const { return QgsAbstractValidityCheck::TypeLayoutCheck; }
30+
31+
bool QgsLayoutMapCrsValidityCheck::prepareCheck( const QgsValidityCheckContext *context, QgsFeedback * )
32+
{
33+
if ( context->type() != QgsValidityCheckContext::TypeLayoutContext )
34+
return false;
35+
36+
const QgsLayoutValidityCheckContext *layoutContext = static_cast< const QgsLayoutValidityCheckContext * >( context );
2637
if ( !layoutContext )
27-
return results;
38+
return false;
2839

2940
QList< QgsLayoutItemMap * > mapItems;
3041
layoutContext->layout->layoutItems( mapItems );
@@ -36,9 +47,14 @@ QList<QgsValidityCheckResult> QgsLayoutMapCrsValidityCheck::runCheck( const QgsV
3647
res.type = QgsValidityCheckResult::Warning;
3748
res.title = tr( "Map projection is misleading" );
3849
res.detailedDescription = tr( "The projection for the map item %1 is set to <i>Web Mercator (EPSG:3857)</i> which misrepresents areas and shapes. Consider using an appropriate local projection instead." ).arg( map->displayName() );
39-
results.append( res );
50+
mResults.append( res );
4051
}
4152
}
4253

43-
return results;
54+
return true;
55+
}
56+
57+
QList<QgsValidityCheckResult> QgsLayoutMapCrsValidityCheck::runCheck( const QgsValidityCheckContext *, QgsFeedback * )
58+
{
59+
return mResults;
4460
}

src/app/layout/qgslayoutvaliditychecks.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ class QgsLayoutMapCrsValidityCheck : public QgsAbstractValidityCheck
2222

2323
public:
2424

25-
QString id() const override { return "map_crs_check"; }
26-
int checkType() const override { return QgsAbstractValidityCheck::TypeLayoutCheck; }
27-
QString name() const override { return "Map CRS Check"; }
28-
QList< QgsValidityCheckResult > runCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback ) const override;
29-
30-
31-
32-
25+
QgsLayoutMapCrsValidityCheck *create() const override;
26+
QString id() const override;
27+
int checkType() const override;
28+
bool prepareCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback ) override;
29+
QList< QgsValidityCheckResult > runCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback ) override;
30+
31+
private:
32+
QList<QgsValidityCheckResult> mResults;
3333
};

src/core/CMakeLists.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -774,8 +774,6 @@ SET(QGIS_CORE_MOC_HDRS
774774
layertree/qgslayertreenode.h
775775
layertree/qgslayertreeregistrybridge.h
776776

777-
validity/qgsabstractvaliditycheck.h
778-
779777
qgsuserprofilemanager.h
780778
)
781779

@@ -1193,6 +1191,7 @@ SET(QGIS_CORE_HDRS
11931191
metadata/qgslayermetadata.h
11941192
metadata/qgslayermetadatavalidator.h
11951193

1194+
validity/qgsabstractvaliditycheck.h
11961195
validity/qgsvaliditycheckcontext.h
11971196
validity/qgsvaliditycheckregistry.h
11981197
)

src/core/validity/qgsabstractvaliditycheck.h

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ class CORE_EXPORT QgsValidityCheckResult
8181
* an export to occur, or validating the contents of a Processing model (and warning if required plugin based
8282
* providers are not available or if compulsory algorithm parameters have not been populated).
8383
*
84-
* Subclasses must indicate the type of check the represent via the checkType() method. When checks are performed,
84+
* Subclasses must indicate the type of check they represent via the checkType() method. When checks are performed,
8585
* all the registered checks with a matching check type are performed sequentially. The check type also
8686
* dictates the subclass of the QgsValidityCheckContext which is given to the subclass' runCheck method.
8787
*
@@ -94,7 +94,6 @@ class CORE_EXPORT QgsValidityCheckResult
9494
*/
9595
class CORE_EXPORT QgsAbstractValidityCheck : public QObject
9696
{
97-
Q_OBJECT
9897

9998
public:
10099

@@ -105,8 +104,15 @@ class CORE_EXPORT QgsAbstractValidityCheck : public QObject
105104
TypeUserCheck = 10000, //!< Starting point for custom user types
106105
};
107106

107+
/**
108+
* Creates a new instance of the check and returns it.
109+
*/
110+
virtual QgsAbstractValidityCheck *create() const = 0 SIP_FACTORY;
111+
108112
/**
109113
* Returns the unique ID of the check.
114+
*
115+
* This is a non-translated, non-user visible string identifying the check.
110116
*/
111117
virtual QString id() const = 0;
112118

@@ -116,17 +122,39 @@ class CORE_EXPORT QgsAbstractValidityCheck : public QObject
116122
virtual int checkType() const = 0;
117123

118124
/**
119-
* Returns the name of the check.
125+
* Prepares the check for execution, and returns true if the check can be run.
126+
*
127+
* This method is always called from the main thread, and subclasses can implement
128+
* it to do preparatory steps which are not thread safe (e.g. obtaining feature
129+
* sources from vector layers). It is followed by a call to runCheck(), which
130+
* may be performed in a background thread.
131+
*
132+
* Individual calls to prepareCheck()/runCheck() are run on a new instance of the
133+
* check (see create()), so subclasses can safely store state from the prepareCheck() method
134+
* ready for the subsequent runCheck() method.
135+
*
136+
* The \a context argument gives the wider in which the check is being run.
120137
*/
121-
virtual QString name() const = 0;
138+
virtual bool prepareCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback )
139+
{
140+
Q_UNUSED( context );
141+
Q_UNUSED( feedback );
142+
return true;
143+
}
122144

123145
/**
124146
* Runs the check and returns a list of results. If the check is "passed" and no warnings or errors are generated,
125147
* then an empty list should be returned.
126148
*
149+
* This method may be called in a background thread, so subclasses should take care to ensure that
150+
* only thread-safe methods are used. It is always preceeded by a call to prepareCheck().
151+
*
152+
* If a check needs to perform non-thread-safe tests, these should be implemented within prepareCheck()
153+
* and stored in the subclass instance to be returned by this method.
154+
*
127155
* The \a context argument gives the wider in which the check is being run.
128156
*/
129-
virtual QList< QgsValidityCheckResult > runCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback ) const = 0;
157+
virtual QList< QgsValidityCheckResult > runCheck( const QgsValidityCheckContext *context, QgsFeedback *feedback ) = 0;
130158

131159
};
132160

src/core/validity/qgsvaliditycheckcontext.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,18 @@ class CORE_EXPORT QgsValidityCheckContext
4545
#endif
4646

4747
public:
48-
// initially nothing in the base class!
48+
49+
//! Available check context types
50+
enum ContextType
51+
{
52+
TypeLayoutContext = 1, //!< Layout context, see QgsLayoutValidityCheckContext
53+
TypeUserContext = 10001, //!< Starting point for user contexts
54+
};
55+
56+
/**
57+
* Returns the context type.
58+
*/
59+
virtual int type() const = 0;
4960

5061
virtual ~QgsValidityCheckContext() = default;
5162

@@ -72,6 +83,8 @@ class CORE_EXPORT QgsLayoutValidityCheckContext : public QgsValidityCheckContext
7283
: layout( layout )
7384
{}
7485

86+
int type() const override { return TypeLayoutContext; }
87+
7588
/**
7689
* Pointer to the layout which the check is being run against.
7790
*/

src/core/validity/qgsvaliditycheckregistry.cpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ QgsValidityCheckRegistry::~QgsValidityCheckRegistry()
2525
qDeleteAll( mChecks );
2626
}
2727

28-
QList<QgsAbstractValidityCheck *> QgsValidityCheckRegistry::checks() const
28+
QList<const QgsAbstractValidityCheck *> QgsValidityCheckRegistry::checks() const
2929
{
30-
QList<QgsAbstractValidityCheck *> results;
30+
QList<const QgsAbstractValidityCheck *> results;
3131
for ( const QPointer< QgsAbstractValidityCheck > &check : mChecks )
3232
{
3333
if ( check )
@@ -36,9 +36,9 @@ QList<QgsAbstractValidityCheck *> QgsValidityCheckRegistry::checks() const
3636
return results;
3737
}
3838

39-
QList<QgsAbstractValidityCheck *> QgsValidityCheckRegistry::checks( int type ) const
39+
QList<const QgsAbstractValidityCheck *> QgsValidityCheckRegistry::checks( int type ) const
4040
{
41-
QList< QgsAbstractValidityCheck * > results;
41+
QList< const QgsAbstractValidityCheck * > results;
4242
for ( const QPointer< QgsAbstractValidityCheck > &check : mChecks )
4343
{
4444
if ( check && check->checkType() == type )
@@ -62,10 +62,22 @@ void QgsValidityCheckRegistry::removeCheck( QgsAbstractValidityCheck *check )
6262
QList<QgsValidityCheckResult> QgsValidityCheckRegistry::runChecks( int type, const QgsValidityCheckContext *context, QgsFeedback *feedback ) const
6363
{
6464
QList<QgsValidityCheckResult> result;
65-
const QList<QgsAbstractValidityCheck *> toCheck = checks( type );
65+
const std::vector<std::unique_ptr<QgsAbstractValidityCheck> > toCheck = createChecks( type );
6666
int i = 0;
67-
for ( QgsAbstractValidityCheck *check : toCheck )
67+
for ( const std::unique_ptr< QgsAbstractValidityCheck > &check : toCheck )
6868
{
69+
if ( feedback && feedback->isCanceled() )
70+
break;
71+
72+
if ( !check->prepareCheck( context, feedback ) )
73+
{
74+
if ( feedback )
75+
{
76+
feedback->setProgress( static_cast< double >( i ) / toCheck.size() * 100 );
77+
}
78+
continue;
79+
}
80+
6981
const QList< QgsValidityCheckResult > checkResults = check->runCheck( context, feedback );
7082
for ( QgsValidityCheckResult checkResult : checkResults )
7183
{
@@ -75,11 +87,20 @@ QList<QgsValidityCheckResult> QgsValidityCheckRegistry::runChecks( int type, con
7587
i++;
7688
if ( feedback )
7789
{
78-
if ( feedback->isCanceled() )
79-
break;
80-
81-
feedback->setProgress( static_cast< double >( i ) / toCheck.count() * 100 );
90+
feedback->setProgress( static_cast< double >( i ) / toCheck.size() * 100 );
8291
}
8392
}
8493
return result;
8594
}
95+
96+
std::vector<std::unique_ptr<QgsAbstractValidityCheck> > QgsValidityCheckRegistry::createChecks( int type ) const
97+
{
98+
const QList< const QgsAbstractValidityCheck *> toCheck = checks( type );
99+
std::vector<std::unique_ptr<QgsAbstractValidityCheck> > results;
100+
results.reserve( toCheck.size() );
101+
for ( const QgsAbstractValidityCheck *check : toCheck )
102+
{
103+
results.emplace_back( std::unique_ptr< QgsAbstractValidityCheck >( check->create() ) );
104+
}
105+
return results;
106+
}

0 commit comments

Comments
 (0)