Skip to content

Commit 5c02c8a

Browse files
authoredMar 13, 2017
Merge pull request #4238 from nyalldawson/canvas_theme
Allow setting map canvases to auto follow a map theme
2 parents cd7b19c + 9842fcb commit 5c02c8a

File tree

8 files changed

+334
-274
lines changed

8 files changed

+334
-274
lines changed
 

‎python/gui/qgsmapcanvas.sip

Lines changed: 32 additions & 255 deletions
Original file line numberDiff line numberDiff line change
@@ -18,303 +18,83 @@ class QgsMapCanvas : QGraphicsView
1818
public:
1919

2020
//! Constructor
21-
QgsMapCanvas( QWidget * parent /TransferThis/ = 0 );
22-
21+
QgsMapCanvas( QWidget *parent /TransferThis/ = 0 );
2322
~QgsMapCanvas();
24-
25-
//! Returns the magnification factor
26-
//! @note added in 2.16
2723
double magnificationFactor() const;
28-
29-
//! Set list of layers that should be shown in the canvas
30-
//! @note added in 3.0
31-
void setLayers( const QList<QgsMapLayer*>& layers );
32-
33-
void setCurrentLayer( QgsMapLayer* layer );
34-
35-
//! Get access to properties used for map rendering
36-
//! @note added in 2.4
37-
const QgsMapSettings& mapSettings() const /KeepReference/;
38-
39-
//! sets destination coordinate reference system
40-
//! @note added in 2.4
41-
void setDestinationCrs( const QgsCoordinateReferenceSystem& crs );
42-
43-
//! Get access to the labeling results (may be null)
44-
//! @note added in 2.4
45-
const QgsLabelingResults* labelingResults() const;
46-
47-
//! Set whether to cache images of rendered layers
48-
//! @note added in 2.4
24+
void setLayers( const QList<QgsMapLayer *> &layers );
25+
void setCurrentLayer( QgsMapLayer *layer );
26+
const QgsMapSettings &mapSettings() const /KeepReference/;
27+
void setDestinationCrs( const QgsCoordinateReferenceSystem &crs );
28+
const QgsLabelingResults *labelingResults() const;
4929
void setCachingEnabled( bool enabled );
50-
51-
//! Check whether images of rendered layers are curerently being cached
52-
//! @note added in 2.4
5330
bool isCachingEnabled() const;
54-
55-
//! Make sure to remove any rendered images from cache (does nothing if cache is not enabled)
56-
//! @note added in 2.4
5731
void clearCache();
58-
59-
//! Reload all layers, clear the cache and refresh the canvas
60-
//! @note added in 2.9
6132
void refreshAllLayers();
62-
63-
//! Set whether the layers are rendered in parallel or sequentially
64-
//! @note added in 2.4
33+
void waitWhileRendering();
6534
void setParallelRenderingEnabled( bool enabled );
66-
67-
//! Check whether the layers are rendered in parallel or sequentially
68-
//! @note added in 2.4
6935
bool isParallelRenderingEnabled() const;
70-
71-
//! Set how often map preview should be updated while it is being rendered (in milliseconds)
72-
//! @note added in 2.4
7336
void setMapUpdateInterval( int timeMilliseconds );
74-
75-
//! Find out how often map preview should be updated while it is being rendered (in milliseconds)
76-
//! @note added in 2.4
7737
int mapUpdateInterval() const;
78-
79-
//! Get the last reported scale of the canvas
8038
double scale();
81-
82-
//! Returns the mapUnitsPerPixel (map units per pixel) for the canvas
8339
double mapUnitsPerPixel() const;
84-
85-
//! Returns the current zoom extent of the map canvas
8640
QgsRectangle extent() const;
87-
//! Returns the combined extent for all layers on the map canvas
8841
QgsRectangle fullExtent() const;
89-
90-
//! Set the extent of the map canvas
9142
void setExtent( const QgsRectangle &r, bool magnified = false );
92-
93-
//! Get the current map canvas rotation in clockwise degrees
94-
//! @note added in 2.8
9543
double rotation() const;
96-
97-
//! Set the rotation of the map canvas in clockwise degrees
98-
//! @note added in 2.8
9944
void setRotation( double degrees );
100-
101-
//! Set the center of the map canvas, in geographical coordinates
102-
//! @note added in 2.8
103-
void setCenter( const QgsPoint& center );
104-
105-
//! Get map center, in geographical coordinates
106-
//! @note added in 2.8
45+
void setCenter( const QgsPoint &center );
10746
QgsPoint center() const;
108-
109-
//! Zoom to the full extent of all layers
11047
void zoomToFullExtent();
111-
112-
//! Zoom to the previous extent (view)
11348
void zoomToPreviousExtent();
114-
115-
//! Zoom to the next extent (view)
11649
void zoomToNextExtent();
117-
118-
// ! Clears the list of extents and sets current extent as first item
11950
void clearExtentHistory();
120-
121-
/** Zoom to the extent of the selected features of current (vector) layer.
122-
* @param layer optionally specify different than current layer
123-
*/
124-
void zoomToSelected( QgsVectorLayer* layer = 0 );
125-
126-
/** Set canvas extent to the bounding box of a set of features
127-
@param layer the vector layer
128-
@param ids the feature ids*/
129-
void zoomToFeatureIds( QgsVectorLayer* layer, const QgsFeatureIds& ids );
130-
131-
/** Centers canvas extent to feature ids
132-
@param layer the vector layer
133-
@param ids the feature ids*/
134-
void panToFeatureIds( QgsVectorLayer* layer, const QgsFeatureIds& ids );
135-
136-
//! Pan to the selected features of current (vector) layer keeping same extent.
137-
void panToSelected( QgsVectorLayer* layer = 0 );
138-
139-
//! \brief Sets the map tool currently being used on the canvas
140-
void setMapTool( QgsMapTool* mapTool );
141-
142-
/** \brief Unset the current map tool or last non zoom tool
143-
*
144-
* This is called from destructor of map tools to make sure
145-
* that this map tool won't be used any more.
146-
* You don't have to call it manualy, QgsMapTool takes care of it.
147-
*/
148-
void unsetMapTool( QgsMapTool* mapTool );
149-
150-
//! Returns the currently active tool
151-
QgsMapTool* mapTool();
152-
153-
//! Write property of QColor bgColor.
154-
void setCanvasColor( const QColor & _newVal );
155-
//! Read property of QColor bgColor.
51+
void zoomToSelected( QgsVectorLayer *layer = 0 );
52+
void zoomToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids );
53+
void panToFeatureIds( QgsVectorLayer *layer, const QgsFeatureIds &ids );
54+
void panToSelected( QgsVectorLayer *layer = 0 );
55+
void setMapTool( QgsMapTool *mapTool );
56+
void unsetMapTool( QgsMapTool *mapTool );
57+
QgsMapTool *mapTool();
58+
void setCanvasColor( const QColor &_newVal );
15659
QColor canvasColor() const;
157-
158-
//! Set color of selected vector features
159-
//! @note added in 2.4
160-
void setSelectionColor( const QColor& color );
161-
162-
//! Emits signal scaleChanged to update scale in main window
60+
void setSelectionColor( const QColor &color );
16361
void updateScale();
164-
165-
//! return the map layer at position index in the layer stack
16662
QgsMapLayer *layer( int index );
167-
168-
//! return number of layers on the map
16963
int layerCount() const;
170-
171-
//! return list of layers within map canvas.
172-
QList<QgsMapLayer*> layers() const;
64+
QList<QgsMapLayer *> layers() const;
17365
void freeze( bool frozen = true );
17466
bool isFrozen() const;
17567
bool renderFlag() const;
176-
177-
//! Get the current canvas map units
17868
QgsUnitTypes::DistanceUnit mapUnits() const;
179-
180-
//! Getter for stored overrides of styles for layers.
181-
//! @note added in 2.12
18269
QMap<QString, QString> layerStyleOverrides() const;
183-
184-
//! Setter for stored overrides of styles for layers.
185-
//! @note added in 2.12
186-
void setLayerStyleOverrides( const QMap<QString, QString>& overrides );
187-
188-
//! Get the current coordinate transform
189-
const QgsMapToPixel* getCoordinateTransform();
190-
191-
//! Find out whether rendering is in progress
70+
void setLayerStyleOverrides( const QMap<QString, QString> &overrides );
71+
void setTheme( const QString &theme );
72+
QString theme() const;
73+
const QgsMapToPixel *getCoordinateTransform();
19274
bool isDrawing();
193-
194-
//! returns current layer (set by legend widget)
195-
QgsMapLayer* currentLayer();
196-
197-
//! set wheel zoom factor (should be greater than 1)
75+
QgsMapLayer *currentLayer();
19876
void setWheelFactor( double factor );
199-
200-
//! Zoom to a specific scale
20177
void zoomScale( double scale );
202-
203-
//! Zoom with the factor supplied. Factor > 1 zooms out, interval (0,1) zooms in
204-
//! If point is given, re-center on it
20578
void zoomByFactor( double scaleFactor, const QgsPoint *center = 0 );
206-
207-
//! Zooms in/out with a given center
20879
void zoomWithCenter( int x, int y, bool zoomIn );
209-
210-
//! Zooms to feature extent. Adds a small margin around the extent
211-
//! and does a pan if rect is empty (point extent)
212-
void zoomToFeatureExtent( QgsRectangle& rect );
213-
214-
//! Returns whether the scale is locked, so zooming can be performed using magnication.
215-
//! @note added in 2.16
216-
//! @see setScaleLocked()
80+
void zoomToFeatureExtent( QgsRectangle &rect );
21781
bool scaleLocked() const;
218-
219-
//! used to determine if anti-aliasing is enabled or not
22082
void enableAntiAliasing( bool flag );
221-
222-
//! true if antialising is enabled
22383
bool antiAliasingEnabled() const;
224-
225-
//! sets map tile rendering flag
22684
void enableMapTileRendering( bool flag );
227-
228-
// following 2 methods should be moved elsewhere or changed to private
229-
// currently used by pan map tool
230-
//! Ends pan action and redraws the canvas.
23185
void panActionEnd( QPoint releasePoint );
232-
233-
//! Called when mouse is moving and pan is activated
234-
void panAction( QMouseEvent * event );
235-
236-
//! returns last position of mouse cursor
86+
void panAction( QMouseEvent *event );
23787
QPoint mouseLastXY();
238-
239-
/** Enables a preview mode for the map canvas
240-
* @param previewEnabled set to true to enable a preview mode
241-
* @see setPreviewMode
242-
* @note added in 2.3 */
24388
void setPreviewModeEnabled( bool previewEnabled );
244-
245-
/** Returns whether a preview mode is enabled for the map canvas
246-
* @returns true if a preview mode is currently enabled
247-
* @see setPreviewModeEnabled
248-
* @see previewMode
249-
* @note added in 2.3 */
25089
bool previewModeEnabled() const;
251-
252-
/** Sets a preview mode for the map canvas. This setting only has an effect if
253-
* previewModeEnabled is true.
254-
* @param mode preview mode for the canvas
255-
* @see previewMode
256-
* @see setPreviewModeEnabled
257-
* @see previewModeEnabled
258-
* @note added in 2.3 */
25990
void setPreviewMode( QgsPreviewEffect::PreviewMode mode );
260-
261-
/** Returns the current preview mode for the map canvas. This setting only has an effect if
262-
* previewModeEnabled is true.
263-
* @returns preview mode for map canvas
264-
* @see setPreviewMode
265-
* @see previewModeEnabled
266-
* @note added in 2.3 */
26791
QgsPreviewEffect::PreviewMode previewMode() const;
268-
269-
/** Return snapping utility class that is associated with map canvas.
270-
* If no snapping utils instance has been associated previously, an internal will be created for convenience
271-
* (so map tools do not need to test for existence of the instance).
272-
*
273-
* Main canvas in QGIS returns an instance which is always up-to-date with the project's snapping configuration.
274-
* @note added in 2.8
275-
*/
276-
QgsSnappingUtils* snappingUtils() const;
277-
278-
/** Assign an instance of snapping utils to the map canvas.
279-
* The instance is not owned by the canvas, so it is possible to use one instance in multiple canvases.
280-
*
281-
* For main canvas in QGIS, do not associate a different instance from the existing one (it is updated from
282-
* the project's snapping configuration).
283-
* @note added in 2.8
284-
*/
285-
void setSnappingUtils( QgsSnappingUtils* utils );
286-
287-
/** Sets an expression context scope for the map canvas. This scope is injected into the expression
288-
* context used for rendering the map, and can be used to apply specific variable overrides for
289-
* expression evaluation for the map canvas render. This method will overwrite the existing expression
290-
* context scope for the canvas.
291-
* @param scope new expression context scope
292-
* @note added in QGIS 2.12
293-
* @see expressionContextScope()
294-
*/
295-
void setExpressionContextScope( const QgsExpressionContextScope& scope );
296-
297-
/** Returns a reference to the expression context scope for the map canvas. This scope is injected
298-
* into the expression context used for rendering the map, and can be used to apply specific variable
299-
* overrides for expression evaluation for the map canvas render.
300-
* @note added in QGIS 2.12
301-
* @see setExpressionContextScope()
302-
*/
303-
QgsExpressionContextScope& expressionContextScope();
304-
305-
/** Returns a const reference to the expression context scope for the map canvas.
306-
* @note added in QGIS 2.12
307-
* @see setExpressionContextScope()
308-
* @note not available in python bindings
309-
*/
310-
// const QgsExpressionContextScope& expressionContextScope() const;
311-
312-
/** Sets the segmentation tolerance applied when rendering curved geometries
313-
@param tolerance the segmentation tolerance*/
92+
QgsSnappingUtils *snappingUtils() const;
93+
void setSnappingUtils( QgsSnappingUtils *utils );
94+
void setExpressionContextScope( const QgsExpressionContextScope &scope );
95+
QgsExpressionContextScope &expressionContextScope();
96+
// const QgsExpressionContextScope &expressionContextScope() const;
31497
void setSegmentationTolerance( double tolerance );
315-
316-
/** Sets segmentation tolerance type (maximum angle or maximum difference between curve and approximation)
317-
@param type the segmentation tolerance typename*/
31898
void setSegmentationToleranceType( QgsAbstractGeometry::SegmentationToleranceType type );
31999

320100
public slots:
@@ -434,12 +214,9 @@ class QgsMapCanvas : QGraphicsView
434214
//! @note added in 2.8
435215
void currentLayerChanged( QgsMapLayer* layer );
436216

437-
//! Emitted when the configuration of overridden layer styles changes
438-
//! @note added in 2.12
439217
void layerStyleOverridesChanged();
440-
441-
//! emit a message (usually to be displayed in a message bar)
442-
void messageEmitted( const QString& title, const QString& message, QgsMessageBar::MessageLevel = QgsMessageBar::INFO );
218+
void themeChanged( const QString &theme );
219+
void messageEmitted( const QString &title, const QString &message, QgsMessageBar::MessageLevel = QgsMessageBar::INFO );
443220

444221
protected:
445222

‎src/gui/qgsmapcanvas.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ email : sherman at mrcc.com
6363
#include "qgsrubberband.h"
6464
#include "qgsvectorlayer.h"
6565
#include "qgscursors.h"
66+
#include "qgsmapthemecollection.h"
6667
#include <cmath>
6768

6869

@@ -140,6 +141,8 @@ QgsMapCanvas::QgsMapCanvas( QWidget *parent )
140141
connect( QgsProject::instance(), &QgsProject::writeProject,
141142
this, &QgsMapCanvas::writeProject );
142143

144+
connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsMapCanvas::mapThemeChanged );
145+
143146
mSettings.setFlag( QgsMapSettings::DrawEditingInfo );
144147
mSettings.setFlag( QgsMapSettings::UseRenderingOptimization );
145148
mSettings.setFlag( QgsMapSettings::RenderPartialOutput );
@@ -288,6 +291,15 @@ const QgsMapToPixel *QgsMapCanvas::getCoordinateTransform()
288291
}
289292

290293
void QgsMapCanvas::setLayers( const QList<QgsMapLayer *> &layers )
294+
{
295+
// following a theme => request denied!
296+
if ( !mTheme.isEmpty() )
297+
return;
298+
299+
setLayersPrivate( layers );
300+
}
301+
302+
void QgsMapCanvas::setLayersPrivate( const QList<QgsMapLayer *> &layers )
291303
{
292304
QList<QgsMapLayer *> oldLayers = mSettings.layers();
293305

@@ -482,6 +494,17 @@ void QgsMapCanvas::refreshMap()
482494

483495
mSettings.setExpressionContext( expressionContext );
484496

497+
if ( !mTheme.isEmpty() )
498+
{
499+
// IMPORTANT: we MUST set the layer style overrides here! (At the time of writing this
500+
// comment) retrieving layer styles from the theme collection gives an XML snapshot of the
501+
// current state of the style. If we had stored the style overrides earlier (such as in
502+
// mapThemeChanged slot) then this xml could be out of date...
503+
// TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
504+
// just return the style name, we can instead set the overrides in mapThemeChanged and not here
505+
mSettings.setLayerStyleOverrides( QgsProject::instance()->mapThemeCollection()->mapThemeStyleOverrides( mTheme ) );
506+
}
507+
485508
// create the renderer job
486509
Q_ASSERT( !mJob );
487510
mJobCanceled = false;
@@ -518,6 +541,26 @@ void QgsMapCanvas::refreshMap()
518541
emit renderStarting();
519542
}
520543

544+
void QgsMapCanvas::mapThemeChanged( const QString &theme )
545+
{
546+
if ( theme == mTheme )
547+
{
548+
// set the canvas layers to match the new layers contained in the map theme
549+
// NOTE: we do this when the theme layers change and not when we are refreshing the map
550+
// as setLayers() sets up necessary connections to handle changes to the layers
551+
setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
552+
// IMPORTANT: we don't set the layer style overrides here! (At the time of writing this
553+
// comment) retrieving layer styles from the theme collection gives an XML snapshot of the
554+
// current state of the style. If changes were made to the style then this xml
555+
// snapshot goes out of sync...
556+
// TODO: if in future QgsMapThemeCollection::mapThemeStyleOverrides is changed to
557+
// just return the style name, we can instead set the overrides here and not in refreshMap()
558+
559+
clearCache();
560+
refresh();
561+
}
562+
}
563+
521564

522565
void QgsMapCanvas::rendererJobFinished()
523566
{
@@ -1595,6 +1638,25 @@ void QgsMapCanvas::setLayerStyleOverrides( const QMap<QString, QString> &overrid
15951638
emit layerStyleOverridesChanged();
15961639
}
15971640

1641+
void QgsMapCanvas::setTheme( const QString &theme )
1642+
{
1643+
if ( mTheme == theme )
1644+
return;
1645+
1646+
clearCache();
1647+
if ( theme.isEmpty() || !QgsProject::instance()->mapThemeCollection()->hasMapTheme( theme ) )
1648+
{
1649+
mTheme.clear();
1650+
mSettings.setLayerStyleOverrides( QMap< QString, QString>() );
1651+
emit themeChanged( QString() );
1652+
}
1653+
else
1654+
{
1655+
mTheme = theme;
1656+
setLayersPrivate( QgsProject::instance()->mapThemeCollection()->mapThemeVisibleLayers( mTheme ) );
1657+
emit themeChanged( theme );
1658+
}
1659+
}
15981660

15991661
void QgsMapCanvas::setRenderFlag( bool flag )
16001662
{
@@ -1954,6 +2016,14 @@ void QgsMapCanvas::refreshAllLayers()
19542016
refresh();
19552017
}
19562018

2019+
void QgsMapCanvas::waitWhileRendering()
2020+
{
2021+
while ( mRefreshScheduled || mJob )
2022+
{
2023+
QgsApplication::processEvents();
2024+
}
2025+
}
2026+
19572027
void QgsMapCanvas::setSegmentationTolerance( double tolerance )
19582028
{
19592029
mSettings.setSegmentationTolerance( tolerance );

‎src/gui/qgsmapcanvas.h

Lines changed: 85 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class QgsRubberBand;
7272
class GUI_EXPORT QgsMapCanvas : public QGraphicsView
7373
{
7474
Q_OBJECT
75+
Q_PROPERTY( QString theme READ theme WRITE setTheme NOTIFY themeChanged )
7576

7677
public:
7778

@@ -84,8 +85,17 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
8485
//! @note added in 2.16
8586
double magnificationFactor() const;
8687

87-
//! Set list of layers that should be shown in the canvas
88-
//! @note added in 3.0
88+
/**
89+
* Sets the list of \a layers that should be shown in the canvas.
90+
*
91+
* If the map canvas has been associated with a map theme via a call
92+
* to setTheme(), then any calls to setLayers() are ignored. It is necessary
93+
* to first clear the theme association by calling setTheme() with an
94+
* empty string before setLayers() calls can be made.
95+
*
96+
* @note added in 3.0
97+
* @see layers()
98+
*/
8999
void setLayers( const QList<QgsMapLayer *> &layers );
90100

91101
void setCurrentLayer( QgsMapLayer *layer );
@@ -118,6 +128,17 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
118128
//! @note added in 2.9
119129
void refreshAllLayers();
120130

131+
/**
132+
* Blocks until the rendering job has finished.
133+
*
134+
* In almost all cases you do NOT want to call this, as it will hang the UI
135+
* until the rendering job is complete. It's included in API solely for
136+
* unit testing and standalone python scripts.
137+
*
138+
* @note added in QGIS 3.0
139+
*/
140+
void waitWhileRendering();
141+
121142
//! Set whether the layers are rendered in parallel or sequentially
122143
//! @note added in 2.4
123144
void setParallelRenderingEnabled( bool enabled );
@@ -226,7 +247,10 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
226247
//! return number of layers on the map
227248
int layerCount() const;
228249

229-
//! return list of layers within map canvas.
250+
/**
251+
* Return the list of layers shown within the map canvas.
252+
* @see setLayers()
253+
*/
230254
QList<QgsMapLayer *> layers() const;
231255

232256
/**
@@ -265,14 +289,55 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
265289
*/
266290
QgsUnitTypes::DistanceUnit mapUnits() const;
267291

268-
//! Getter for stored overrides of styles for layers.
269-
//! @note added in 2.12
292+
/**
293+
* Returns the stored overrides of styles for layers.
294+
* @note added in 2.12
295+
* @see setLayerStyleOverrides().
296+
*/
270297
QMap<QString, QString> layerStyleOverrides() const;
271298

272-
//! Setter for stored overrides of styles for layers.
273-
//! @note added in 2.12
299+
/**
300+
* Sets the stored overrides of styles for rendering layers.
301+
*
302+
* If the map canvas has been associated with a map theme via a call
303+
* to setTheme(), then any calls to setLayerStyleOverrides() are ignored. It is necessary
304+
* to first clear the theme association by calling setTheme() with an
305+
* empty string before setLayerStyleOverrides() calls can be made.
306+
*
307+
* @note added in 2.12
308+
* @see layerStyleOverrides()
309+
*/
274310
void setLayerStyleOverrides( const QMap<QString, QString> &overrides );
275311

312+
/**
313+
* Sets a map \a theme to show in the canvas. The theme name must match
314+
* a theme present in the associated project's QgsMapThemeCollection.
315+
*
316+
* When the canvas is associated to a map theme, it will automatically follow
317+
* the layer selection and layer styles from that theme. Calls to setLayers()
318+
* or setLayerStyleOverrides() will have no effect, and canvases associated
319+
* with a QgsLayerTreeMapCanvasBridge will no longer synchronize their
320+
* state with the layer tree. In these cases it is necessary to call
321+
* setTheme() with an empty string to clear the theme association and
322+
* allow map updates with setLayers(), setLayerStyleOverrides(), or via
323+
* QgsLayerTreeMapCanvasBridge.
324+
*
325+
* If an empty string is passed then the current theme association will be
326+
* cleared. The layers from the previously associated theme will remain
327+
* in the canvas, and a call to setLayers() may be necessary to define
328+
* which layers should be shown in the canvas.
329+
* @note added in QGIS 3.0
330+
* @see theme()
331+
*/
332+
void setTheme( const QString &theme );
333+
334+
/**
335+
* Returns the map's theme shown in the canvas, if set.
336+
* @note added in QGIS 3.0
337+
* @see setTheme()
338+
*/
339+
QString theme() const { return mTheme; }
340+
276341
//! Get the current coordinate transform
277342
const QgsMapToPixel *getCoordinateTransform();
278343

@@ -472,6 +537,8 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
472537

473538
void refreshMap();
474539

540+
void mapThemeChanged( const QString &theme );
541+
475542
signals:
476543

477544
/** Emits current mouse position
@@ -548,6 +615,13 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
548615
//! @note added in 2.12
549616
void layerStyleOverridesChanged();
550617

618+
/**
619+
* Emitted when the canvas has been assigned a different map theme.
620+
* @see setTheme()
621+
* @note added in QGIS 3.0
622+
*/
623+
void themeChanged( const QString &theme );
624+
551625
//! emit a message (usually to be displayed in a message bar)
552626
void messageEmitted( const QString &title, const QString &message, QgsMessageBar::MessageLevel = QgsMessageBar::INFO );
553627

@@ -708,6 +782,8 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
708782

709783
QTimer mAutoRefreshTimer;
710784

785+
QString mTheme;
786+
711787
//! Force a resize of the map canvas item
712788
//! @note added in 2.16
713789
void updateMapSize();
@@ -732,6 +808,8 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView
732808
@return true in case of success*/
733809
bool boundingBoxOfFeatureIds( const QgsFeatureIds &ids, QgsVectorLayer *layer, QgsRectangle &bbox, QString &errorMsg ) const;
734810

811+
void setLayersPrivate( const QList<QgsMapLayer *> &layers );
812+
735813
friend class TestQgsMapCanvas;
736814

737815
}; // class QgsMapCanvas

‎tests/src/python/test_qgsmapcanvas.py

Lines changed: 147 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919
QgsVectorLayer,
2020
QgsFeature,
2121
QgsGeometry,
22-
QgsMultiRenderChecker)
22+
QgsMultiRenderChecker,
23+
QgsFillSymbol,
24+
QgsSingleSymbolRenderer,
25+
QgsMapThemeCollection,
26+
QgsProject)
2327
from qgis.gui import (QgsMapCanvas)
2428

2529
from qgis.PyQt.QtCore import QDir
@@ -54,13 +58,10 @@ def testDeferredUpdate(self):
5458
canvas.setLayers([layer])
5559
canvas.setExtent(QgsRectangle(10, 30, 20, 35))
5660
canvas.show()
57-
5861
# need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
5962
while not canvas.isDrawing():
6063
app.processEvents()
61-
while canvas.isDrawing():
62-
app.processEvents()
63-
64+
canvas.waitWhileRendering()
6465
self.assertTrue(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))
6566

6667
# add polygon to layer
@@ -79,10 +80,7 @@ def testDeferredUpdate(self):
7980

8081
# refresh canvas
8182
canvas.refresh()
82-
while not canvas.isDrawing():
83-
app.processEvents()
84-
while canvas.isDrawing():
85-
app.processEvents()
83+
canvas.waitWhileRendering()
8684

8785
# now we expect the canvas check to fail (since they'll be a new polygon rendered over it)
8886
self.assertFalse(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))
@@ -106,9 +104,7 @@ def testRefreshOnTimer(self):
106104
# need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
107105
while not canvas.isDrawing():
108106
app.processEvents()
109-
while canvas.isDrawing():
110-
app.processEvents()
111-
107+
canvas.waitWhileRendering()
112108
self.assertTrue(self.canvasImageCheck('empty_canvas', 'empty_canvas', canvas))
113109

114110
# add polygon to layer
@@ -179,6 +175,145 @@ def testCancelAndDestroy(self):
179175
canvas.stopRendering()
180176
del canvas
181177

178+
def testMapTheme(self):
179+
canvas = QgsMapCanvas()
180+
canvas.setDestinationCrs(QgsCoordinateReferenceSystem(4326))
181+
canvas.setFrameStyle(0)
182+
canvas.resize(600, 400)
183+
self.assertEqual(canvas.width(), 600)
184+
self.assertEqual(canvas.height(), 400)
185+
186+
layer = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
187+
"layer", "memory")
188+
# add a polygon to layer
189+
f = QgsFeature()
190+
f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
191+
self.assertTrue(layer.dataProvider().addFeatures([f]))
192+
193+
# create a style
194+
sym1 = QgsFillSymbol.createSimple({'color': '#ffb200'})
195+
renderer = QgsSingleSymbolRenderer(sym1)
196+
layer.setRenderer(renderer)
197+
198+
canvas.setLayers([layer])
199+
canvas.setExtent(QgsRectangle(10, 30, 20, 35))
200+
canvas.show()
201+
202+
# need to wait until first redraw can occur (note that we first need to wait till drawing starts!)
203+
while not canvas.isDrawing():
204+
app.processEvents()
205+
canvas.waitWhileRendering()
206+
self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))
207+
208+
# add some styles
209+
layer.styleManager().addStyleFromLayer('style1')
210+
sym2 = QgsFillSymbol.createSimple({'color': '#00b2ff'})
211+
renderer2 = QgsSingleSymbolRenderer(sym2)
212+
layer.setRenderer(renderer2)
213+
layer.styleManager().addStyleFromLayer('style2')
214+
215+
canvas.refresh()
216+
canvas.waitWhileRendering()
217+
self.assertTrue(self.canvasImageCheck('theme2', 'theme2', canvas))
218+
219+
layer.styleManager().setCurrentStyle('style1')
220+
canvas.refresh()
221+
canvas.waitWhileRendering()
222+
self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))
223+
224+
# ok, so all good with setting/rendering map styles
225+
# try setting canvas to a particular theme
226+
227+
# make some themes...
228+
theme1 = QgsMapThemeCollection.MapThemeRecord()
229+
record1 = QgsMapThemeCollection.MapThemeLayerRecord(layer)
230+
record1.currentStyle = 'style1'
231+
record1.usingCurrentStyle = True
232+
theme1.setLayerRecords([record1])
233+
234+
theme2 = QgsMapThemeCollection.MapThemeRecord()
235+
record2 = QgsMapThemeCollection.MapThemeLayerRecord(layer)
236+
record2.currentStyle = 'style2'
237+
record2.usingCurrentStyle = True
238+
theme2.setLayerRecords([record2])
239+
240+
QgsProject.instance().mapThemeCollection().insert('theme1', theme1)
241+
QgsProject.instance().mapThemeCollection().insert('theme2', theme2)
242+
243+
canvas.setTheme('theme2')
244+
canvas.refresh()
245+
canvas.waitWhileRendering()
246+
self.assertTrue(self.canvasImageCheck('theme2', 'theme2', canvas))
247+
248+
canvas.setTheme('theme1')
249+
canvas.refresh()
250+
canvas.waitWhileRendering()
251+
self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))
252+
253+
# add another layer
254+
layer2 = QgsVectorLayer("Polygon?crs=epsg:4326&field=fldtxt:string",
255+
"layer2", "memory")
256+
f = QgsFeature()
257+
f.setGeometry(QgsGeometry.fromRect(QgsRectangle(5, 25, 25, 45)))
258+
self.assertTrue(layer2.dataProvider().addFeatures([f]))
259+
260+
# create a style
261+
sym1 = QgsFillSymbol.createSimple({'color': '#b2ff00'})
262+
renderer = QgsSingleSymbolRenderer(sym1)
263+
layer2.setRenderer(renderer)
264+
265+
# rerender canvas - should NOT show new layer
266+
canvas.refresh()
267+
canvas.waitWhileRendering()
268+
self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))
269+
# test again - this time refresh all layers
270+
canvas.refreshAllLayers()
271+
canvas.waitWhileRendering()
272+
self.assertTrue(self.canvasImageCheck('theme1', 'theme1', canvas))
273+
274+
# add layer 2 to theme1
275+
record3 = QgsMapThemeCollection.MapThemeLayerRecord(layer2)
276+
theme1.setLayerRecords([record3])
277+
QgsProject.instance().mapThemeCollection().update('theme1', theme1)
278+
279+
canvas.refresh()
280+
canvas.waitWhileRendering()
281+
self.assertTrue(self.canvasImageCheck('theme3', 'theme3', canvas))
282+
283+
# change the appearance of an active style
284+
layer2.styleManager().addStyleFromLayer('original')
285+
layer2.styleManager().addStyleFromLayer('style4')
286+
record3.currentStyle = 'style4'
287+
record3.usingCurrentStyle = True
288+
theme1.setLayerRecords([record3])
289+
QgsProject.instance().mapThemeCollection().update('theme1', theme1)
290+
291+
canvas.refresh()
292+
canvas.waitWhileRendering()
293+
self.assertTrue(self.canvasImageCheck('theme3', 'theme3', canvas))
294+
295+
layer2.styleManager().setCurrentStyle('style4')
296+
sym3 = QgsFillSymbol.createSimple({'color': '#b200b2'})
297+
layer2.renderer().setSymbol(sym3)
298+
canvas.refresh()
299+
canvas.waitWhileRendering()
300+
self.assertTrue(self.canvasImageCheck('theme4', 'theme4', canvas))
301+
302+
# try setting layers while a theme is in place
303+
canvas.setLayers([layer])
304+
canvas.refresh()
305+
306+
# should be no change... setLayers should be ignored if canvas is following a theme!
307+
canvas.waitWhileRendering()
308+
self.assertTrue(self.canvasImageCheck('theme4', 'theme4', canvas))
309+
310+
# setLayerStyleOverrides while theme is in place
311+
canvas.setLayerStyleOverrides({layer2.id(): 'original'})
312+
# should be no change... setLayerStyleOverrides should be ignored if canvas is following a theme!
313+
canvas.refresh()
314+
canvas.waitWhileRendering()
315+
self.assertTrue(self.canvasImageCheck('theme4', 'theme4', canvas))
316+
182317
def canvasImageCheck(self, name, reference_image, canvas):
183318
self.report += "<h2>Render {}</h2>\n".format(name)
184319
temp_dir = QDir.tempPath() + '/'
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)
Please sign in to comment.