Skip to content

Commit 74381b8

Browse files
committed
Add signal when a map theme changes, add tests
1 parent 72cc2ee commit 74381b8

File tree

5 files changed

+139
-8
lines changed

5 files changed

+139
-8
lines changed

python/core/qgsmapthemecollection.sip

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,10 @@ class QgsMapThemeCollection : QObject
160160
void setProject( QgsProject* project );
161161
signals:
162162

163-
/** Emitted when map themes within the collection are changed.
164-
*/
165163
void mapThemesChanged();
164+
165+
void mapThemeChanged( const QString &theme );
166+
166167
void projectChanged();
167168
};
168169

src/core/qgsmapthemecollection.cpp

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
QgsMapThemeCollection::QgsMapThemeCollection( QgsProject *project )
3030
: mProject( project )
3131
{
32-
connect( project, &QgsProject::layersRemoved, this, &QgsMapThemeCollection::registryLayersRemoved );
32+
connect( project, static_cast<void ( QgsProject::* )( const QStringList & )>( &QgsProject::layersWillBeRemoved ), this, &QgsMapThemeCollection::registryLayersRemoved );
3333
}
3434

3535
QgsMapThemeCollection::MapThemeLayerRecord QgsMapThemeCollection::createThemeLayerRecord( QgsLayerTreeLayer *nodeLayer, QgsLayerTreeModel *model )
@@ -169,9 +169,9 @@ void QgsMapThemeCollection::setProject( QgsProject *project )
169169
if ( project == mProject )
170170
return;
171171

172-
disconnect( mProject, &QgsProject::layersRemoved, this, &QgsMapThemeCollection::registryLayersRemoved );
172+
disconnect( mProject, static_cast<void ( QgsProject::* )( const QStringList & )>( &QgsProject::layersWillBeRemoved ), this, &QgsMapThemeCollection::registryLayersRemoved );
173173
mProject = project;
174-
connect( mProject, &QgsProject::layersRemoved, this, &QgsMapThemeCollection::registryLayersRemoved );
174+
connect( mProject, static_cast<void ( QgsProject::* )( const QStringList & )>( &QgsProject::layersWillBeRemoved ), this, &QgsMapThemeCollection::registryLayersRemoved );
175175
emit projectChanged();
176176
}
177177

@@ -186,6 +186,7 @@ void QgsMapThemeCollection::insert( const QString &name, const QgsMapThemeCollec
186186
mMapThemes.insert( name, state );
187187

188188
reconnectToLayersStyleManager();
189+
emit mapThemeChanged( name );
189190
emit mapThemesChanged();
190191
}
191192

@@ -197,6 +198,7 @@ void QgsMapThemeCollection::update( const QString &name, const MapThemeRecord &s
197198
mMapThemes[name] = state;
198199

199200
reconnectToLayersStyleManager();
201+
emit mapThemeChanged( name );
200202
emit mapThemesChanged();
201203
}
202204

@@ -316,7 +318,7 @@ void QgsMapThemeCollection::reconnectToLayersStyleManager()
316318

317319
Q_FOREACH ( QgsMapLayer *ml, layers )
318320
{
319-
connect( ml->styleManager(), SIGNAL( styleRenamed( QString, QString ) ), this, SLOT( layerStyleRenamed( QString, QString ) ) );
321+
connect( ml->styleManager(), &QgsMapLayerStyleManager::styleRenamed, this, &QgsMapThemeCollection::layerStyleRenamed );
320322
}
321323
}
322324

@@ -375,6 +377,7 @@ void QgsMapThemeCollection::readXml( const QDomDocument &doc )
375377
MapThemeRecord rec;
376378
rec.setLayerRecords( layerRecords.values() );
377379
mMapThemes.insert( presetName, rec );
380+
emit mapThemeChanged( presetName );
378381

379382
visPresetElem = visPresetElem.nextSiblingElement( QStringLiteral( "visibility-preset" ) );
380383
}
@@ -426,8 +429,9 @@ void QgsMapThemeCollection::writeXml( QDomDocument &doc )
426429

427430
void QgsMapThemeCollection::registryLayersRemoved( const QStringList &layerIDs )
428431
{
429-
// TODO: this should not be necessary - layers are stored as weak pointers
430-
432+
// while layers are stored as weak pointers, this triggers the mapThemeChanged signal for
433+
// affected themes
434+
QSet< QString > changedThemes;
431435
MapThemeRecordMap::iterator it = mMapThemes.begin();
432436
for ( ; it != mMapThemes.end(); ++it )
433437
{
@@ -436,9 +440,17 @@ void QgsMapThemeCollection::registryLayersRemoved( const QStringList &layerIDs )
436440
{
437441
MapThemeLayerRecord &layerRec = rec.mLayerRecords[i];
438442
if ( layerRec.layer() && layerIDs.contains( layerRec.layer()->id() ) )
443+
{
439444
rec.mLayerRecords.removeAt( i-- );
445+
changedThemes << it.key();
446+
}
440447
}
441448
}
449+
450+
Q_FOREACH ( const QString &theme, changedThemes )
451+
{
452+
emit mapThemeChanged( theme );
453+
}
442454
emit mapThemesChanged();
443455
}
444456

@@ -448,6 +460,8 @@ void QgsMapThemeCollection::layerStyleRenamed( const QString &oldName, const QSt
448460
if ( !styleMgr )
449461
return;
450462

463+
QSet< QString > changedThemes;
464+
451465
MapThemeRecordMap::iterator it = mMapThemes.begin();
452466
for ( ; it != mMapThemes.end(); ++it )
453467
{
@@ -458,10 +472,17 @@ void QgsMapThemeCollection::layerStyleRenamed( const QString &oldName, const QSt
458472
if ( layerRec.layer() == styleMgr->layer() )
459473
{
460474
if ( layerRec.currentStyle == oldName )
475+
{
461476
layerRec.currentStyle = newName;
477+
changedThemes << it.key();
478+
}
462479
}
463480
}
464481
}
482+
Q_FOREACH ( const QString &theme, changedThemes )
483+
{
484+
emit mapThemeChanged( theme );
485+
}
465486
emit mapThemesChanged();
466487
}
467488

src/core/qgsmapthemecollection.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,12 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject
250250
*/
251251
void mapThemesChanged();
252252

253+
/**
254+
* Emitted when a map theme changes definition.
255+
* @note added in QGIS 3.0
256+
*/
257+
void mapThemeChanged( const QString &theme );
258+
253259
/**
254260
* Emitted when the project changes
255261
*

tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ ADD_PYTHON_TEST(PyQgsMapLayer test_qgsmaplayer.py)
7070
ADD_PYTHON_TEST(PyQgsMapLayerModel test_qgsmaplayermodel.py)
7171
ADD_PYTHON_TEST(PyQgsMapRenderer test_qgsmaprenderer.py)
7272
ADD_PYTHON_TEST(PyQgsMapRendererCache test_qgsmaprenderercache.py)
73+
ADD_PYTHON_TEST(PyQgsMapThemeCollection test_qgsmapthemecollection.py)
7374
ADD_PYTHON_TEST(PyQgsMapUnitScale test_qgsmapunitscale.py)
7475
ADD_PYTHON_TEST(PyQgsMargins test_qgsmargins.py)
7576
ADD_PYTHON_TEST(PyQgsMemoryProvider test_provider_memory.py)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsMapThemeCollection.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Nyall Dawson'
10+
__date__ = '8/03/2017'
11+
__copyright__ = 'Copyright 2017, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
import qgis # NOQA
16+
17+
from qgis.core import (QgsMapThemeCollection,
18+
QgsProject,
19+
QgsVectorLayer)
20+
from qgis.testing import start_app, unittest
21+
from qgis.PyQt.QtTest import QSignalSpy
22+
23+
app = start_app()
24+
25+
26+
class TestQgsMapThemeCollection(unittest.TestCase):
27+
28+
def setUp(self):
29+
pass
30+
31+
def testThemeChanged(self):
32+
"""
33+
Test that the mapTheme(s)Changed signals are correctly emitted in all relevant situations
34+
"""
35+
project = QgsProject()
36+
collection = QgsMapThemeCollection(project)
37+
38+
record = QgsMapThemeCollection.MapThemeRecord()
39+
40+
theme_changed_spy = QSignalSpy(collection.mapThemeChanged)
41+
themes_changed_spy = QSignalSpy(collection.mapThemesChanged)
42+
43+
collection.insert('theme1', record)
44+
self.assertEqual(len(theme_changed_spy), 1)
45+
self.assertEqual(theme_changed_spy[-1][0], 'theme1')
46+
self.assertEqual(len(themes_changed_spy), 1)
47+
48+
# reinsert
49+
collection.insert('theme1', record)
50+
self.assertEqual(len(theme_changed_spy), 2)
51+
self.assertEqual(theme_changed_spy[-1][0], 'theme1')
52+
self.assertEqual(len(themes_changed_spy), 2)
53+
54+
# update
55+
collection.update('theme1', record)
56+
self.assertEqual(len(theme_changed_spy), 3)
57+
self.assertEqual(theme_changed_spy[-1][0], 'theme1')
58+
self.assertEqual(len(themes_changed_spy), 3)
59+
60+
# remove invalid
61+
collection.removeMapTheme('i wish i was a slave to an age old trade... like riding around on rail cars and working long days')
62+
self.assertEqual(len(theme_changed_spy), 3)
63+
self.assertEqual(len(themes_changed_spy), 3)
64+
# remove valid
65+
collection.removeMapTheme('theme1')
66+
self.assertEqual(len(theme_changed_spy), 3) # not changed - removed!
67+
self.assertEqual(len(themes_changed_spy), 4)
68+
69+
# reinsert
70+
collection.insert('theme1', record)
71+
self.assertEqual(len(theme_changed_spy), 4)
72+
self.assertEqual(len(themes_changed_spy), 5)
73+
74+
# clear
75+
collection.clear()
76+
self.assertEqual(len(theme_changed_spy), 4) # not changed - removed!
77+
self.assertEqual(len(themes_changed_spy), 6)
78+
79+
# check that mapThemeChanged is emitted if layer is removed
80+
layer = QgsVectorLayer("Point?field=fldtxt:string",
81+
"layer1", "memory")
82+
layer2 = QgsVectorLayer("Point?field=fldtxt:string",
83+
"layer2", "memory")
84+
project.addMapLayers([layer, layer2])
85+
86+
# record for layer1
87+
record.addLayerRecord(QgsMapThemeCollection.MapThemeLayerRecord(layer))
88+
collection.insert('theme1', record)
89+
self.assertEqual(len(theme_changed_spy), 5)
90+
self.assertEqual(len(themes_changed_spy), 7)
91+
92+
# now kill layer 2
93+
project.removeMapLayer(layer2)
94+
self.assertEqual(len(theme_changed_spy), 5) # signal should not be emitted - layer is not in record
95+
# now kill layer 1
96+
project.removeMapLayer(layer)
97+
app.processEvents()
98+
self.assertEqual(len(theme_changed_spy), 6) # signal should be emitted - layer is in record
99+
100+
101+
if __name__ == '__main__':
102+
unittest.main()

0 commit comments

Comments
 (0)