diff --git a/python/core/auto_generated/qgsmapthemecollection.sip.in b/python/core/auto_generated/qgsmapthemecollection.sip.in index 70f35fe7419..1891581f008 100644 --- a/python/core/auto_generated/qgsmapthemecollection.sip.in +++ b/python/core/auto_generated/qgsmapthemecollection.sip.in @@ -186,14 +186,22 @@ Updates a map theme within the collection. void removeMapTheme( const QString &name ); %Docstring -Remove an existing map theme from collection. +Removes an existing map theme from collection. .. versionadded:: 3.0 +%End + + bool renameMapTheme( const QString &name, const QString &newName ); +%Docstring +Renames the existing map theme called ``name`` to ``newName``. +Returns ``True`` if the rename was successful, or ``False`` if it failed (e.g. due to a duplicate name for ``newName``). + +.. versionadded:: 3.14 %End void clear(); %Docstring -Remove all map themes from the collection. +Removes all map themes from the collection. %End QStringList mapThemes() const; @@ -324,6 +332,13 @@ Emitted when map themes within the collection are changed. Emitted when a map theme changes definition. .. versionadded:: 3.0 +%End + + void mapThemeRenamed( const QString &name, const QString &newName ); +%Docstring +Emitted when a map theme within the collection is renamed. + +.. versionadded:: 3.14 %End void projectChanged(); diff --git a/src/app/3d/qgs3dmapcanvasdockwidget.cpp b/src/app/3d/qgs3dmapcanvasdockwidget.cpp index d0c10d07cbd..f9e7b79c4ca 100644 --- a/src/app/3d/qgs3dmapcanvasdockwidget.cpp +++ b/src/app/3d/qgs3dmapcanvasdockwidget.cpp @@ -104,6 +104,7 @@ Qgs3DMapCanvasDockWidget::Qgs3DMapCanvasDockWidget( QWidget *parent ) // Map Theme Menu mMapThemeMenu = new QMenu(); connect( mMapThemeMenu, &QMenu::aboutToShow, this, &Qgs3DMapCanvasDockWidget::mapThemeMenuAboutToShow ); + connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &Qgs3DMapCanvasDockWidget::currentMapThemeRenamed ); mBtnMapThemes = new QToolButton(); mBtnMapThemes->setAutoRaise( true ); @@ -345,3 +346,11 @@ void Qgs3DMapCanvasDockWidget::mapThemeMenuAboutToShow() } mMapThemeMenu->addActions( mMapThemeMenuPresetActions ); } + +void Qgs3DMapCanvasDockWidget::currentMapThemeRenamed( const QString &theme, const QString &newTheme ) +{ + if ( theme == mCanvas->map()->terrainMapTheme() ) + { + mCanvas->map()->setTerrainMapTheme( newTheme ); + } +} diff --git a/src/app/3d/qgs3dmapcanvasdockwidget.h b/src/app/3d/qgs3dmapcanvasdockwidget.h index 457f010dee4..ac0b26d59bd 100644 --- a/src/app/3d/qgs3dmapcanvasdockwidget.h +++ b/src/app/3d/qgs3dmapcanvasdockwidget.h @@ -65,6 +65,8 @@ class APP_EXPORT Qgs3DMapCanvasDockWidget : public QgsDockWidget void onMainCanvasColorChanged(); void onTotalPendingJobsCountChanged(); void mapThemeMenuAboutToShow(); + //! Renames the active map theme called \a theme to \a newTheme + void currentMapThemeRenamed( const QString &theme, const QString &newTheme ); private: Qgs3DMapCanvas *mCanvas = nullptr; diff --git a/src/app/qgsmapcanvasdockwidget.cpp b/src/app/qgsmapcanvasdockwidget.cpp index 3b5165cb29b..e521ed7792f 100644 --- a/src/app/qgsmapcanvasdockwidget.cpp +++ b/src/app/qgsmapcanvasdockwidget.cpp @@ -227,6 +227,8 @@ QgsMapCanvasDockWidget::QgsMapCanvasDockWidget( const QString &name, QWidget *pa if ( mSyncExtentRadio->isChecked() ) syncViewCenter( mMainCanvas ); } ); + + connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsMapCanvasDockWidget::currentMapThemeRenamed ); } void QgsMapCanvasDockWidget::setMainCanvas( QgsMapCanvas *canvas ) @@ -441,6 +443,14 @@ void QgsMapCanvasDockWidget::menuAboutToShow() mMenu->addActions( mMenuPresetActions ); } +void QgsMapCanvasDockWidget::currentMapThemeRenamed( const QString &theme, const QString &newTheme ) +{ + if ( theme == mMapCanvas->theme() ) + { + mMapCanvas->setTheme( newTheme ); + } +} + void QgsMapCanvasDockWidget::settingsMenuAboutToShow() { whileBlocking( mActionShowAnnotations )->setChecked( mMapCanvas->annotationsVisible() ); diff --git a/src/app/qgsmapcanvasdockwidget.h b/src/app/qgsmapcanvasdockwidget.h index 398a0e68abf..a912ccd9c38 100644 --- a/src/app/qgsmapcanvasdockwidget.h +++ b/src/app/qgsmapcanvasdockwidget.h @@ -157,6 +157,8 @@ class APP_EXPORT QgsMapCanvasDockWidget : public QgsDockWidget, private Ui::QgsM void mapExtentChanged(); void mapCrsChanged(); void menuAboutToShow(); + //! Renames the active map theme called \a theme to \a newTheme + void currentMapThemeRenamed( const QString &theme, const QString &newTheme ); void settingsMenuAboutToShow(); void syncMarker( const QgsPointXY &p ); void mapScaleChanged(); diff --git a/src/app/qgsmapthemes.cpp b/src/app/qgsmapthemes.cpp index 1151e4ee444..66a23467660 100644 --- a/src/app/qgsmapthemes.cpp +++ b/src/app/qgsmapthemes.cpp @@ -49,6 +49,7 @@ QgsMapThemes::QgsMapThemes() mReplaceMenu = new QMenu( tr( "Replace Theme" ) ); mMenu->addMenu( mReplaceMenu ); + mActionRenameCurrentPreset = mMenu->addAction( tr( "Rename Current Theme…" ), this, &QgsMapThemes::renameCurrentPreset ); mActionAddPreset = mMenu->addAction( tr( "Add Theme…" ), this, [ = ] { addPreset(); } ); mMenuSeparator = mMenu->addSeparator(); @@ -140,6 +141,32 @@ void QgsMapThemes::applyState( const QString &presetName ) QgsProject::instance()->mapThemeCollection()->applyTheme( presetName, root, model ); } +void QgsMapThemes::renameCurrentPreset() +{ + QgsMapThemeCollection::MapThemeRecord mapTheme = currentState(); + QStringList existingNames = QgsProject::instance()->mapThemeCollection()->mapThemes(); + + for ( QAction *actionPreset : qgis::as_const( mMenuPresetActions ) ) + { + if ( actionPreset->isChecked() ) + { + QgsNewNameDialog dlg( + tr( "theme" ), + tr( "%1" ).arg( actionPreset->text() ), + QStringList(), existingNames, QRegExp(), Qt::CaseInsensitive, mMenu ); + + dlg.setWindowTitle( tr( "Rename Map Theme" ) ); + dlg.setHintString( tr( "Enter the new name of the map theme" ) ); + dlg.setOverwriteEnabled( false ); + dlg.setConflictingNameWarning( tr( "A theme with this name already exists." ) ); + if ( dlg.exec() != QDialog::Accepted || dlg.name().isEmpty() ) + return; + + QgsProject::instance()->mapThemeCollection()->renameMapTheme( actionPreset->text(), dlg.name() ); + } + } +} + void QgsMapThemes::removeCurrentPreset() { for ( QAction *actionPreset : qgis::as_const( mMenuPresetActions ) ) @@ -189,4 +216,5 @@ void QgsMapThemes::menuAboutToShow() mActionAddPreset->setEnabled( !hasCurrent ); mActionRemoveCurrentPreset->setEnabled( hasCurrent ); + mActionRenameCurrentPreset->setEnabled( hasCurrent ); } diff --git a/src/app/qgsmapthemes.h b/src/app/qgsmapthemes.h index 1a1f09e4331..84a36adae59 100644 --- a/src/app/qgsmapthemes.h +++ b/src/app/qgsmapthemes.h @@ -65,6 +65,9 @@ class APP_EXPORT QgsMapThemes : public QObject //! Handles removal of current preset from the project's collection void removeCurrentPreset(); + //! Handles renaming of the current map theme + void renameCurrentPreset(); + //! Handles creation of preset menu void menuAboutToShow(); @@ -91,6 +94,7 @@ class APP_EXPORT QgsMapThemes : public QObject QAction *mMenuSeparator = nullptr; QAction *mActionAddPreset = nullptr; QAction *mActionRemoveCurrentPreset = nullptr; + QAction *mActionRenameCurrentPreset = nullptr; QList mMenuPresetActions; QList mMenuReplaceActions; }; diff --git a/src/core/layout/qgslayoutitemmap.cpp b/src/core/layout/qgslayoutitemmap.cpp index 2ca83fcb619..87a4f653660 100644 --- a/src/core/layout/qgslayoutitemmap.cpp +++ b/src/core/layout/qgslayoutitemmap.cpp @@ -1825,6 +1825,14 @@ void QgsLayoutItemMap::mapThemeChanged( const QString &theme ) mCachedLayerStyleOverridesPresetName.clear(); // force cache regeneration at next redraw } +void QgsLayoutItemMap::currentMapThemeRenamed( const QString &theme, const QString &newTheme ) +{ + if ( theme == mFollowVisibilityPresetName ) + { + mFollowVisibilityPresetName = newTheme; + } +} + void QgsLayoutItemMap::connectUpdateSlot() { //connect signal from layer registry to update in case of new or deleted layers @@ -1867,6 +1875,7 @@ void QgsLayoutItemMap::connectUpdateSlot() } ); connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsLayoutItemMap::mapThemeChanged ); + connect( project->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsLayoutItemMap::currentMapThemeRenamed ); } QTransform QgsLayoutItemMap::layoutToMapCoordsTransform() const diff --git a/src/core/layout/qgslayoutitemmap.h b/src/core/layout/qgslayoutitemmap.h index ab11a339ce0..6a784418c54 100644 --- a/src/core/layout/qgslayoutitemmap.h +++ b/src/core/layout/qgslayoutitemmap.h @@ -637,6 +637,9 @@ class CORE_EXPORT QgsLayoutItemMap : public QgsLayoutItem, public QgsTemporalRan void mapThemeChanged( const QString &theme ); + //! Renames the active map theme called \a theme to \a newTheme + void currentMapThemeRenamed( const QString &theme, const QString &newTheme ); + //! Create cache image void recreateCachedImageInBackground(); diff --git a/src/core/qgsmapthemecollection.cpp b/src/core/qgsmapthemecollection.cpp index c6da0af31ac..09e7d013721 100644 --- a/src/core/qgsmapthemecollection.cpp +++ b/src/core/qgsmapthemecollection.cpp @@ -281,6 +281,19 @@ void QgsMapThemeCollection::update( const QString &name, const MapThemeRecord &s emit mapThemesChanged(); } +bool QgsMapThemeCollection::renameMapTheme( const QString &name, const QString &newName ) +{ + if ( !mMapThemes.contains( name ) || mMapThemes.contains( newName ) ) + return false; + + const MapThemeRecord state = mMapThemes[name]; + const MapThemeRecord newState = state; + insert( newName, newState ); + emit mapThemeRenamed( name, newName ); + removeMapTheme( name ); + return true; +} + void QgsMapThemeCollection::removeMapTheme( const QString &name ) { if ( !mMapThemes.contains( name ) ) diff --git a/src/core/qgsmapthemecollection.h b/src/core/qgsmapthemecollection.h index 12389afeee3..741ca0e3dfb 100644 --- a/src/core/qgsmapthemecollection.h +++ b/src/core/qgsmapthemecollection.h @@ -257,12 +257,19 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject void update( const QString &name, const QgsMapThemeCollection::MapThemeRecord &state ); /** - * Remove an existing map theme from collection. + * Removes an existing map theme from collection. * \since QGIS 3.0 */ void removeMapTheme( const QString &name ); - //! Remove all map themes from the collection. + /** + * Renames the existing map theme called \a name to \a newName. + * Returns TRUE if the rename was successful, or FALSE if it failed (e.g. due to a duplicate name for \a newName). + * \since QGIS 3.14 + */ + bool renameMapTheme( const QString &name, const QString &newName ); + + //! Removes all map themes from the collection. void clear(); /** @@ -373,6 +380,12 @@ class CORE_EXPORT QgsMapThemeCollection : public QObject */ void mapThemeChanged( const QString &theme ); + /** + * Emitted when a map theme within the collection is renamed. + * \since QGIS 3.14 + */ + void mapThemeRenamed( const QString &name, const QString &newName ); + /** * Emitted when the project changes * diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp index bc401227621..adc7ca1c51c 100644 --- a/src/gui/qgsmapcanvas.cpp +++ b/src/gui/qgsmapcanvas.cpp @@ -138,6 +138,7 @@ QgsMapCanvas::QgsMapCanvas( QWidget *parent ) this, &QgsMapCanvas::writeProject ); connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeChanged, this, &QgsMapCanvas::mapThemeChanged ); + connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemeRenamed, this, &QgsMapCanvas::mapThemeRenamed ); connect( QgsProject::instance()->mapThemeCollection(), &QgsMapThemeCollection::mapThemesChanged, this, &QgsMapCanvas::projectThemesChanged ); mSettings.setFlag( QgsMapSettings::DrawEditingInfo ); @@ -615,6 +616,17 @@ void QgsMapCanvas::mapThemeChanged( const QString &theme ) } } +void QgsMapCanvas::mapThemeRenamed( const QString &theme, const QString &newTheme ) +{ + if ( mTheme.isEmpty() || theme != mTheme ) + { + return; + } + + setTheme( newTheme ); + refresh(); +} + void QgsMapCanvas::rendererJobFinished() { QgsDebugMsgLevel( QStringLiteral( "CANVAS finish! %1" ).arg( !mJobCanceled ), 2 ); diff --git a/src/gui/qgsmapcanvas.h b/src/gui/qgsmapcanvas.h index 0dd29fd8454..565750702df 100644 --- a/src/gui/qgsmapcanvas.h +++ b/src/gui/qgsmapcanvas.h @@ -835,6 +835,8 @@ class GUI_EXPORT QgsMapCanvas : public QGraphicsView void refreshMap(); void mapThemeChanged( const QString &theme ); + //! Renames the active map theme called \a theme to \a newTheme + void mapThemeRenamed( const QString &theme, const QString &newTheme ); signals: diff --git a/tests/src/python/test_qgsmapcanvas.py b/tests/src/python/test_qgsmapcanvas.py index 2fc79bef243..3a46e5992f3 100644 --- a/tests/src/python/test_qgsmapcanvas.py +++ b/tests/src/python/test_qgsmapcanvas.py @@ -331,6 +331,24 @@ class TestQgsMapCanvas(unittest.TestCase): # should be different - we should now render project layers self.assertFalse(self.canvasImageCheck('theme4', 'theme4', canvas)) + # set canvas to theme1 + canvas.setTheme('theme1') + canvas.refresh() + canvas.waitWhileRendering() + self.assertEqual(canvas.theme(), 'theme1') + themeLayers = theme1.layerRecords() + # rename the active theme + QgsProject.instance().mapThemeCollection().renameMapTheme('theme1', 'theme5') + # canvas theme should now be set to theme5 + canvas.refresh() + canvas.waitWhileRendering() + self.assertEqual(canvas.theme(), 'theme5') + # theme5 should render as theme1 + theme5 = QgsProject.instance().mapThemeCollection().mapThemeState('theme5') + theme5Layers = theme5.layerRecords() + self.assertEqual(themeLayers, theme5Layers, 'themes are different') + #self.assertTrue(self.canvasImageCheck('theme5', 'theme5', canvas)) + def canvasImageCheck(self, name, reference_image, canvas): self.report += "

Render {}

\n".format(name) temp_dir = QDir.tempPath() + '/'