diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index 27873f8d307..196781bb523 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -3660,15 +3660,6 @@ Qgis.DatabaseProviderConnectionCapability2.baseClass = Qgis Qgis.DatabaseProviderConnectionCapabilities2.baseClass = Qgis DatabaseProviderConnectionCapabilities2 = Qgis # dirty hack since SIP seems to introduce the flags in module # monkey patching scoped based enum -Qgis.ProviderStyleStorageCapability.SaveToDatabase.__doc__ = "" -Qgis.ProviderStyleStorageCapability.LoadFromDatabase.__doc__ = "" -Qgis.ProviderStyleStorageCapability.DeleteFromDatabase.__doc__ = "" -Qgis.ProviderStyleStorageCapability.__doc__ = "The StorageCapability enum represents the style storage operations supported by the provider.\n\n.. versionadded:: 3.34\n\n" + '* ``SaveToDatabase``: ' + Qgis.ProviderStyleStorageCapability.SaveToDatabase.__doc__ + '\n' + '* ``LoadFromDatabase``: ' + Qgis.ProviderStyleStorageCapability.LoadFromDatabase.__doc__ + '\n' + '* ``DeleteFromDatabase``: ' + Qgis.ProviderStyleStorageCapability.DeleteFromDatabase.__doc__ -# -- -Qgis.ProviderStyleStorageCapability.baseClass = Qgis -Qgis.ProviderStyleStorageCapabilities.baseClass = Qgis -ProviderStyleStorageCapabilities = Qgis # dirty hack since SIP seems to introduce the flags in module -# monkey patching scoped based enum Qgis.UserProfileSelectionPolicy.LastProfile.__doc__ = "Open the last closed profile (only mode supported prior to QGIS 3.32)" Qgis.UserProfileSelectionPolicy.DefaultProfile.__doc__ = "Open a specific profile" Qgis.UserProfileSelectionPolicy.AskUser.__doc__ = "Let the user choose which profile to open" diff --git a/python/core/auto_generated/providers/qgsdataprovider.sip.in b/python/core/auto_generated/providers/qgsdataprovider.sip.in index 1a4704ad72c..ecf3e1bd421 100644 --- a/python/core/auto_generated/providers/qgsdataprovider.sip.in +++ b/python/core/auto_generated/providers/qgsdataprovider.sip.in @@ -467,13 +467,6 @@ String sequence used for separating components of sublayers strings. .. seealso:: :py:func:`subLayers` .. versionadded:: 3.12 -%End - - virtual Qgis::ProviderStyleStorageCapabilities styleStorageCapabilities() const; -%Docstring -Returns the style storage capabilities. - -.. versionadded:: 3.34 %End signals: diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index 19ca3e9bce9..67c899dc3b8 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -2121,15 +2121,6 @@ The development version typedef QFlags DatabaseProviderConnectionCapabilities2; - enum class ProviderStyleStorageCapability - { - SaveToDatabase, - LoadFromDatabase, - DeleteFromDatabase - }; - typedef QFlags ProviderStyleStorageCapabilities; - - enum class UserProfileSelectionPolicy { LastProfile, diff --git a/python/core/auto_generated/qgsmaplayer.sip.in b/python/core/auto_generated/qgsmaplayer.sip.in index bc2505e69f8..11fcc04d24a 100644 --- a/python/core/auto_generated/qgsmaplayer.sip.in +++ b/python/core/auto_generated/qgsmaplayer.sip.in @@ -728,74 +728,6 @@ Read all custom properties from layer. Properties are stored in a map and saved .. versionadded:: 3.14 %End - virtual int listStylesInDatabase( QStringList &ids /Out/, QStringList &names /Out/, - QStringList &descriptions /Out/, QString &msgError /Out/ ); -%Docstring -Lists all the style in db split into related to the layer and not related to - -:param ids: the list in which will be stored the style db ids -:param names: the list in which will be stored the style names - -:return: - the number of styles related to current layer (-1 on not implemented) - - descriptions: the list in which will be stored the style descriptions - - msgError: will be set to a descriptive error message if any occurs - -.. note:: - - Since QGIS 3.2 Styles related to the layer are ordered with the default style first then by update time for Postgres, MySQL and Spatialite. -%End - - virtual QString getStyleFromDatabase( const QString &styleId, QString &msgError /Out/ ); -%Docstring -Returns the named style corresponding to style id provided -%End - - virtual bool deleteStyleFromDatabase( const QString &styleId, QString &msgError /Out/ ); -%Docstring -Deletes a style from the database - -:param styleId: the provider's layer_styles table id of the style to delete - -:return: - ``True`` in case of success - - msgError: will be set to a descriptive error message if any occurs - -.. versionadded:: 3.0 -%End - - virtual void saveStyleToDatabase( const QString &name, const QString &description, - bool useAsDefault, const QString &uiFileContent, - QString &msgError /Out/, - QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ); -%Docstring -Saves named and sld style of the layer to the style table in the db. - -:param name: Style name -:param description: A description of the style -:param useAsDefault: Set to ``True`` if style should be used as the default style for the layer -:param uiFileContent: -:param msgError: will be set to a descriptive error message if any occurs -:param categories: the style categories to be saved. - -.. note:: - - Prior to QGIS 3.24, this method would show a message box warning when a - style with the same ``styleName`` already existed to confirm replacing the style with the user. - Since 3.24, calling this method will ALWAYS overwrite any existing style with the same name. - Use :py:func:`QgsProviderRegistry.styleExists()` to test in advance if a style already exists and handle this appropriately - in your client code. -%End - - virtual QString loadNamedStyle( const QString &theURI, bool &resultFlag /Out/, bool loadFromLocalDb, - QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ); -%Docstring -Loads a named style from file/local db/datasource db - -:param theURI: the URI of the style or the URI of the layer -:param resultFlag: will be set to ``True`` if a named style is correctly loaded -:param loadFromLocalDb: if ``True`` forces to load from local db instead of datasource one -:param categories: the style categories to be loaded. -%End - void removeCustomProperty( const QString &key ); diff --git a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in index 5a6165a6e1f..733976f4aea 100644 --- a/python/core/auto_generated/vector/qgsvectordataprovider.sip.in +++ b/python/core/auto_generated/vector/qgsvectordataprovider.sip.in @@ -541,6 +541,18 @@ Clear recorded errors QStringList errors() const; %Docstring Gets recorded errors +%End + + virtual bool isSaveAndLoadStyleToDatabaseSupported() const; +%Docstring +It returns ``False`` by default. +Must be implemented by providers that support saving and loading styles to db returning ``True`` +%End + + virtual bool isDeleteStyleFromDatabaseSupported() const; +%Docstring +It returns ``False`` by default. +Must be implemented by providers that support delete styles from db returning ``True`` %End virtual QgsFeatureRenderer *createRenderer( const QVariantMap &configuration = QVariantMap() ) const /Factory/; diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index 6bbe2c3f1e8..a1e961f282a 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -939,6 +939,81 @@ Writes vector layer specific state to project file Dom node. Resolves references to other layers (kept as layer IDs after reading XML) into layer objects. .. versionadded:: 3.0 +%End + + virtual void saveStyleToDatabase( const QString &name, const QString &description, + bool useAsDefault, const QString &uiFileContent, + QString &msgError /Out/, + QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ); +%Docstring +Saves named and sld style of the layer to the style table in the db. + +:param name: Style name +:param description: A description of the style +:param useAsDefault: Set to ``True`` if style should be used as the default style for the layer +:param uiFileContent: +:param msgError: will be set to a descriptive error message if any occurs +:param categories: the style categories to be saved. + +.. note:: + + Prior to QGIS 3.24, this method would show a message box warning when a + style with the same ``styleName`` already existed to confirm replacing the style with the user. + Since 3.24, calling this method will ALWAYS overwrite any existing style with the same name. + Use :py:func:`QgsProviderRegistry.styleExists()` to test in advance if a style already exists and handle this appropriately + in your client code. +%End + + virtual int listStylesInDatabase( QStringList &ids /Out/, QStringList &names /Out/, + QStringList &descriptions /Out/, QString &msgError /Out/ ); +%Docstring +Lists all the style in db split into related to the layer and not related to + +:param ids: the list in which will be stored the style db ids +:param names: the list in which will be stored the style names + +:return: - the number of styles related to current layer (-1 on not implemented) + - descriptions: the list in which will be stored the style descriptions + - msgError: will be set to a descriptive error message if any occurs + +.. note:: + + Since QGIS 3.2 Styles related to the layer are ordered with the default style first then by update time for Postgres, MySQL and Spatialite. +%End + + virtual QString getStyleFromDatabase( const QString &styleId, QString &msgError /Out/ ); +%Docstring +Returns the named style corresponding to style id provided +%End + + virtual bool deleteStyleFromDatabase( const QString &styleId, QString &msgError /Out/ ); +%Docstring +Deletes a style from the database + +:param styleId: the provider's layer_styles table id of the style to delete + +:return: - ``True`` in case of success + - msgError: will be set to a descriptive error message if any occurs + +.. versionadded:: 3.0 +%End + + virtual QString loadNamedStyle( const QString &theURI, bool &resultFlag /Out/, bool loadFromLocalDb, + QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ); +%Docstring +Loads a named style from file/local db/datasource db + +:param theURI: the URI of the style or the URI of the layer +:param resultFlag: will be set to ``True`` if a named style is correctly loaded +:param loadFromLocalDb: if ``True`` forces to load from local db instead of datasource one +:param categories: the style categories to be loaded. +%End + + virtual QString loadNamedStyle( const QString &theURI, bool &resultFlag /Out/, + QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ) ${SIP_FINAL}; +%Docstring +Calls loadNamedStyle( theURI, resultFlag, ``False`` ); +Retained for backward compatibility %End bool loadAuxiliaryLayer( const QgsAuxiliaryStorage &storage, const QString &key = QString() ); diff --git a/python/gui/auto_generated/qgslayerpropertiesdialog.sip.in b/python/gui/auto_generated/qgslayerpropertiesdialog.sip.in index 1865306875e..677b2be0934 100644 --- a/python/gui/auto_generated/qgslayerpropertiesdialog.sip.in +++ b/python/gui/auto_generated/qgslayerpropertiesdialog.sip.in @@ -27,7 +27,6 @@ Base class for "layer properties" dialogs, containing common utilities for handl %End public: - QgsLayerPropertiesDialog( QgsMapLayer *layer, QgsMapCanvas *canvas, const QString &settingsKey, QWidget *parent /TransferThis/ = 0, Qt::WindowFlags fl = Qt::WindowFlags(), QgsSettings *settings = 0 ); %Docstring Constructor for QgsLayerPropertiesDialog. @@ -50,27 +49,6 @@ This must be called in order for the standard metadata loading/saving functional virtual void addPropertiesPageFactory( const QgsMapLayerConfigWidgetFactory *factory ); %Docstring Adds properties page from a ``factory``. -%End - - void saveDefaultStyle(); -%Docstring -Saves the default style when appropriate button is pressed - -.. versionadded:: 3.30 -%End - - void loadStyle(); -%Docstring -Triggers a dialog to load a saved style - -.. versionadded:: 3.30 -%End - - void saveStyleAs(); -%Docstring -Saves a style when appriate button is pressed - -.. versionadded:: 3.30 %End public slots: diff --git a/python/gui/auto_generated/raster/qgsrasterlayerproperties.sip.in b/python/gui/auto_generated/raster/qgsrasterlayerproperties.sip.in index 3f7d2e34096..65c9a14105c 100644 --- a/python/gui/auto_generated/raster/qgsrasterlayerproperties.sip.in +++ b/python/gui/auto_generated/raster/qgsrasterlayerproperties.sip.in @@ -52,6 +52,21 @@ Saves the default style when appropriate button is pressed use :py:func:`~QgsRasterLayerProperties.saveStyleAsDefault` instead %End + void loadStyle() /Deprecated/; +%Docstring +Loads a saved style when appropriate button is pressed + +.. deprecated:: + use :py:func:`~QgsRasterLayerProperties.loadStyleFromFile` instead. +%End + + void saveStyleAs(); +%Docstring +Saves a style when appriate button is pressed + +.. versionadded:: 3.30 +%End + protected slots: virtual void optionsStackedWidget_CurrentChanged( int index ) ${SIP_FINAL}; diff --git a/python/gui/auto_generated/vector/qgsvectorlayerproperties.sip.in b/python/gui/auto_generated/vector/qgsvectorlayerproperties.sip.in index 10828f55d26..d0f1be147ce 100644 --- a/python/gui/auto_generated/vector/qgsvectorlayerproperties.sip.in +++ b/python/gui/auto_generated/vector/qgsvectorlayerproperties.sip.in @@ -25,6 +25,34 @@ class QgsVectorLayerProperties : QgsLayerPropertiesDialog virtual bool eventFilter( QObject *obj, QEvent *ev ); + void loadDefaultStyle(); +%Docstring +Loads the default style when appropriate button is pressed + +.. versionadded:: 3.30 +%End + + void saveDefaultStyle(); +%Docstring +Saves the default style when appropriate button is pressed + +.. versionadded:: 3.30 +%End + + void loadStyle(); +%Docstring +Loads a saved style when appropriate button is pressed + +.. versionadded:: 3.30 +%End + + void saveStyleAs(); +%Docstring +Saves a style when appriate button is pressed + +.. versionadded:: 3.30 +%End + protected slots: void optionsStackedWidget_CurrentChanged( int index ) final; virtual void syncToLayer() ${SIP_FINAL}; diff --git a/src/core/providers/gdal/qgsgdalprovider.cpp b/src/core/providers/gdal/qgsgdalprovider.cpp index bead9cc4563..e7aeebe2140 100644 --- a/src/core/providers/gdal/qgsgdalprovider.cpp +++ b/src/core/providers/gdal/qgsgdalprovider.cpp @@ -4202,18 +4202,6 @@ GDALRasterBandH QgsGdalProvider::getBand( int bandNo ) const return GDALGetRasterBand( mGdalDataset, bandNo ); } -Qgis::ProviderStyleStorageCapabilities QgsGdalProvider::styleStorageCapabilities() const -{ - Qgis::ProviderStyleStorageCapabilities storageCapabilities; - if ( isValid() && mDriverName == QLatin1String( "GPKG" ) ) - { - storageCapabilities |= Qgis::ProviderStyleStorageCapability::SaveToDatabase; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::LoadFromDatabase; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::DeleteFromDatabase; - } - return storageCapabilities; -} - // pyramids resampling // see http://www.gdal.org/gdaladdo.html @@ -4552,95 +4540,6 @@ QList QgsGdalProviderMetadata::supportedLayerTypes() const return { Qgis::LayerType::Raster }; } -int QgsGdalProviderMetadata::listStyles( const QString &uri, QStringList &ids, QStringList &names, - QStringList &descriptions, QString &errCause ) -{ - gdal::dataset_unique_ptr ds; - ds.reset( QgsGdalProviderBase::gdalOpen( uri, GDAL_OF_READONLY ) ); - if ( !ds ) - { - errCause = QObject::tr( "Cannot open %1." ).arg( uri ); - return -1; - } - QVariantMap uriParts = QgsGdalProviderBase::decodeGdalUri( uri ); - QString layerName = uriParts["layerName"].toString(); - return QgsOgrUtils::listStyles( ds.get(), layerName, "", ids, names, descriptions, errCause ); -} - -bool QgsGdalProviderMetadata::styleExists( const QString &uri, const QString &styleId, QString &errCause ) -{ - gdal::dataset_unique_ptr ds; - ds.reset( QgsGdalProviderBase::gdalOpen( uri, GDAL_OF_READONLY ) ); - if ( !ds ) - { - errCause = QObject::tr( "Cannot open %1." ).arg( uri ); - return false; - } - QVariantMap uriParts = QgsGdalProviderBase::decodeGdalUri( uri ); - QString layerName = uriParts["layerName"] .toString(); - return QgsOgrUtils::styleExists( ds.get(), layerName, "", styleId, errCause ); -} - -QString QgsGdalProviderMetadata::getStyleById( const QString &uri, const QString &styleId, QString &errCause ) -{ - gdal::dataset_unique_ptr ds; - ds.reset( QgsGdalProviderBase::gdalOpen( uri, GDAL_OF_READONLY ) ); - if ( !ds ) - { - errCause = QObject::tr( "Cannot open %1." ).arg( uri ); - return QString(); - } - return QgsOgrUtils::getStyleById( ds.get(), styleId, errCause ); -} - -bool QgsGdalProviderMetadata::deleteStyleById( const QString &uri, const QString &styleId, QString &errCause ) -{ - gdal::dataset_unique_ptr ds; - ds.reset( QgsGdalProviderBase::gdalOpen( uri, GDAL_OF_READONLY ) ); - if ( !ds ) - { - errCause = QObject::tr( "Cannot open %1." ).arg( uri ); - return false; - } - return QgsOgrUtils::deleteStyleById( ds.get(), styleId, errCause ); -} - -bool QgsGdalProviderMetadata::saveStyle( const QString &uri, const QString &qmlStyle, const QString &sldStyle, - const QString &styleName, const QString &styleDescription, - const QString &uiFileContent, bool useAsDefault, QString &errCause ) -{ - gdal::dataset_unique_ptr ds; - ds.reset( QgsGdalProviderBase::gdalOpen( uri, GDAL_OF_UPDATE ) ); - if ( !ds ) - { - errCause = QObject::tr( "Cannot open %1." ).arg( uri ); - return false; - } - QVariantMap uriParts = QgsGdalProviderBase::decodeGdalUri( uri ); - QString layerName = uriParts["layerName"].toString(); - return QgsOgrUtils::saveStyle( ds.get(), layerName, "", qmlStyle, sldStyle, styleName, styleDescription, uiFileContent, useAsDefault, errCause ); -} - -QString QgsGdalProviderMetadata::loadStyle( const QString &uri, QString &errCause ) -{ - QString name; - return loadStoredStyle( uri, name, errCause ); -} - -QString QgsGdalProviderMetadata::loadStoredStyle( const QString &uri, QString &styleName, QString &errCause ) -{ - gdal::dataset_unique_ptr ds; - ds.reset( QgsGdalProviderBase::gdalOpen( uri, GDAL_OF_READONLY ) ); - if ( !ds ) - { - errCause = QObject::tr( "Cannot open %1." ).arg( uri ); - return QString(); - } - QVariantMap uriParts = QgsGdalProviderBase::decodeGdalUri( uri ); - QString layerName = uriParts["layerName"].toString(); - return QgsOgrUtils::loadStoredStyle( ds.get(), layerName, "", styleName, errCause ); -} - QgsGdalProviderMetadata::QgsGdalProviderMetadata(): QgsProviderMetadata( PROVIDER_KEY, PROVIDER_DESCRIPTION ) { diff --git a/src/core/providers/gdal/qgsgdalprovider.h b/src/core/providers/gdal/qgsgdalprovider.h index e1fd183497c..c5d87a58356 100644 --- a/src/core/providers/gdal/qgsgdalprovider.h +++ b/src/core/providers/gdal/qgsgdalprovider.h @@ -218,8 +218,6 @@ class QgsGdalProvider final: public QgsRasterDataProvider, QgsGdalProviderBase bool setZoomedOutResamplingMethod( ResamplingMethod method ) override { mZoomedOutResamplingMethod = method; return true; } bool setMaxOversampling( double factor ) override { mMaxOversampling = factor; return true; } - Qgis::ProviderStyleStorageCapabilities styleStorageCapabilities() const override; - private: QgsGdalProvider( const QgsGdalProvider &other ); QgsGdalProvider &operator=( const QgsGdalProvider & ) = delete; @@ -404,17 +402,6 @@ class QgsGdalProviderMetadata final: public QgsProviderMetadata QList< QgsProviderSublayerDetails > querySublayers( const QString &uri, Qgis::SublayerQueryFlags flags = Qgis::SublayerQueryFlags(), QgsFeedback *feedback = nullptr ) const override; QStringList sidecarFilesForUri( const QString &uri ) const override; QList< Qgis::LayerType > supportedLayerTypes() const override; - - int listStyles( const QString &uri, QStringList &ids, QStringList &names, - QStringList &descriptions, QString &errCause ) override; - bool styleExists( const QString &uri, const QString &styleId, QString &errCause SIP_OUT ) override; - QString getStyleById( const QString &uri, const QString &styleId, QString &errCause ) override; - bool deleteStyleById( const QString &uri, const QString &styleId, QString &errCause ) override; - bool saveStyle( const QString &uri, const QString &qmlStyle, const QString &sldStyle, - const QString &styleName, const QString &styleDescription, - const QString &uiFileContent, bool useAsDefault, QString &errCause ) override; - QString loadStyle( const QString &uri, QString &errCause ) override; - QString loadStoredStyle( const QString &uri, QString &styleName, QString &errCause ) override; }; ///@endcond diff --git a/src/core/providers/ogr/qgsogrprovider.cpp b/src/core/providers/ogr/qgsogrprovider.cpp index 5ced4436a89..a52f6441398 100644 --- a/src/core/providers/ogr/qgsogrprovider.cpp +++ b/src/core/providers/ogr/qgsogrprovider.cpp @@ -4083,16 +4083,17 @@ bool QgsOgrProvider::leaveUpdateMode() return true; } -Qgis::ProviderStyleStorageCapabilities QgsOgrProvider::styleStorageCapabilities() const +bool QgsOgrProvider::isSaveAndLoadStyleToDatabaseSupported() const { - Qgis::ProviderStyleStorageCapabilities storageCapabilities; - if ( isValid() && ( mGDALDriverName == QLatin1String( "GPKG" ) || mGDALDriverName == QLatin1String( "SQLite" ) ) ) - { - storageCapabilities |= Qgis::ProviderStyleStorageCapability::SaveToDatabase; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::LoadFromDatabase; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::DeleteFromDatabase; - } - return storageCapabilities; + // We could potentially extend support for styling to other drivers + // with multiple layer support. + return mGDALDriverName == QLatin1String( "GPKG" ) || + mGDALDriverName == QLatin1String( "SQLite" ); +} + +bool QgsOgrProvider::isDeleteStyleFromDatabaseSupported() const +{ + return isSaveAndLoadStyleToDatabaseSupported(); } QString QgsOgrProvider::fileVectorFilters() const diff --git a/src/core/providers/ogr/qgsogrprovider.h b/src/core/providers/ogr/qgsogrprovider.h index 3d880ea1e40..0e1ff3d3b5d 100644 --- a/src/core/providers/ogr/qgsogrprovider.h +++ b/src/core/providers/ogr/qgsogrprovider.h @@ -115,7 +115,8 @@ class QgsOgrProvider final: public QgsVectorDataProvider void setEncoding( const QString &e ) override; bool enterUpdateMode() override { return _enterUpdateMode(); } bool leaveUpdateMode() override; - Qgis::ProviderStyleStorageCapabilities styleStorageCapabilities() const override; + bool isSaveAndLoadStyleToDatabaseSupported() const override; + bool isDeleteStyleFromDatabaseSupported() const override; QString fileVectorFilters() const override; bool isValid() const override; QVariant minimumValue( int index ) const override; diff --git a/src/core/providers/ogr/qgsogrprovidermetadata.cpp b/src/core/providers/ogr/qgsogrprovidermetadata.cpp index d8e0b2e8964..b6af1c64012 100644 --- a/src/core/providers/ogr/qgsogrprovidermetadata.cpp +++ b/src/core/providers/ogr/qgsogrprovidermetadata.cpp @@ -339,7 +339,7 @@ QList QgsOgrProviderMetadata::dataItemProviders() const return providers; } -static QgsOgrLayerUniquePtr LoadDataSourceAndLayer( const QString &uri, bool updateMode, QString &filePath, QString &errCause ) +static QgsOgrLayerUniquePtr LoadDataSourceAndLayer( const QString &uri, QString &errCause ) { bool isSubLayer; int layerIndex; @@ -347,87 +347,344 @@ static QgsOgrLayerUniquePtr LoadDataSourceAndLayer( const QString &uri, bool upd QString subsetString; OGRwkbGeometryType ogrGeometryType; QStringList openOptions; - filePath = QgsOgrProviderUtils::analyzeURI( uri, - isSubLayer, - layerIndex, - layerName, - subsetString, - ogrGeometryType, - openOptions ); + QString filePath = QgsOgrProviderUtils::analyzeURI( uri, + isSubLayer, + layerIndex, + layerName, + subsetString, + ogrGeometryType, + openOptions ); - if ( updateMode ) + if ( !layerName.isEmpty() ) { - if ( !layerName.isEmpty() ) - { - return QgsOgrProviderUtils::getLayer( filePath, true, QStringList(), layerName, errCause, true ); - } - else - { - return QgsOgrProviderUtils::getLayer( filePath, true, QStringList(), layerIndex, errCause, true ); - } + return QgsOgrProviderUtils::getLayer( filePath, true, QStringList(), layerName, errCause, true ); } else { - if ( !layerName.isEmpty() ) - { - return QgsOgrProviderUtils::getLayer( filePath, layerName, errCause ); - } - else - { - return QgsOgrProviderUtils::getLayer( filePath, layerIndex, errCause ); - } + return QgsOgrProviderUtils::getLayer( filePath, true, QStringList(), layerIndex, errCause, true ); } } bool QgsOgrProviderMetadata::styleExists( const QString &uri, const QString &styleId, QString &errorCause ) { - QString filePath; - QgsOgrLayerUniquePtr userLayer = LoadDataSourceAndLayer( uri, false, filePath, errorCause ); + errorCause.clear(); + + QgsOgrLayerUniquePtr userLayer = LoadDataSourceAndLayer( uri, errorCause ); if ( !userLayer ) return false; QRecursiveMutex *mutex = nullptr; + OGRLayerH hUserLayer = userLayer->getHandleAndMutex( mutex ); GDALDatasetH hDS = userLayer->getDatasetHandleAndMutex( mutex ); QMutexLocker locker( mutex ); - QString layerName = QString( OGR_L_GetName( hUserLayer ) ); - QString geomColumn = QString( OGR_L_GetGeometryColumn( hUserLayer ) ); - return QgsOgrUtils::styleExists( hDS, layerName, geomColumn, styleId, errorCause ); + // check if layer_styles table exists + OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" ); + if ( !hLayer ) + return false; + + const QString realStyleId = styleId.isEmpty() ? QString( OGR_L_GetName( hUserLayer ) ) : styleId; + + const QString checkQuery = QStringLiteral( "f_table_schema=''" + " AND f_table_name=%1" + " AND f_geometry_column=%2" + " AND styleName=%3" ) + .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetName( hUserLayer ) ) ), + QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetGeometryColumn( hUserLayer ) ) ), + QgsOgrProviderUtils::quotedValue( realStyleId ) ); + OGR_L_SetAttributeFilter( hLayer, checkQuery.toUtf8().constData() ); + OGR_L_ResetReading( hLayer ); + gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) ); + OGR_L_ResetReading( hLayer ); + + if ( hFeature ) + return true; + + return false; } + bool QgsOgrProviderMetadata::saveStyle( const QString &uri, const QString &qmlStyle, const QString &sldStyle, const QString &styleName, const QString &styleDescription, const QString &uiFileContent, bool useAsDefault, QString &errCause ) { - QString filePath; - QgsOgrLayerUniquePtr userLayer = LoadDataSourceAndLayer( uri, true, filePath, errCause ); + QgsOgrLayerUniquePtr userLayer = LoadDataSourceAndLayer( uri, errCause ); if ( !userLayer ) return false; QRecursiveMutex *mutex = nullptr; + OGRLayerH hUserLayer = userLayer->getHandleAndMutex( mutex ); GDALDatasetH hDS = userLayer->getDatasetHandleAndMutex( mutex ); QMutexLocker locker( mutex ); - QString layerName = QString( OGR_L_GetName( hUserLayer ) ); - QString geomColumn = QString( OGR_L_GetGeometryColumn( hUserLayer ) ); - return QgsOgrUtils::saveStyle( hDS, layerName, geomColumn, qmlStyle, sldStyle, styleName, styleDescription, uiFileContent, useAsDefault, errCause ); + // check if layer_styles table already exist + OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" ); + if ( !hLayer ) + { + // if not create it + // Note: we use the same schema as in the SpatiaLite and postgres providers + //for cross interoperability + + char **options = nullptr; + // TODO: might need change if other drivers than GPKG / SQLite + options = CSLSetNameValue( options, "FID", "id" ); + hLayer = GDALDatasetCreateLayer( hDS, "layer_styles", nullptr, wkbNone, options ); + QgsOgrProviderUtils::invalidateCachedDatasets( QString::fromUtf8( GDALGetDescription( hDS ) ) ); + CSLDestroy( options ); + if ( !hLayer ) + { + errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database." ); + return false; + } + bool ok = true; + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_table_catalog", OFTString ) ); + OGR_Fld_SetWidth( fld.get(), 256 ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_table_schema", OFTString ) ); + OGR_Fld_SetWidth( fld.get(), 256 ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_table_name", OFTString ) ); + OGR_Fld_SetWidth( fld.get(), 256 ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_geometry_column", OFTString ) ); + OGR_Fld_SetWidth( fld.get(), 256 ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "styleName", OFTString ) ); + OGR_Fld_SetWidth( fld.get(), 30 ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "styleQML", OFTString ) ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "styleSLD", OFTString ) ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "useAsDefault", OFTInteger ) ); + OGR_Fld_SetSubType( fld.get(), OFSTBoolean ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "description", OFTString ) ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "owner", OFTString ) ); + OGR_Fld_SetWidth( fld.get(), 30 ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "ui", OFTString ) ); + OGR_Fld_SetWidth( fld.get(), 30 ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + { + gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "update_time", OFTDateTime ) ); + OGR_Fld_SetDefault( fld.get(), "CURRENT_TIMESTAMP" ); + ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; + } + if ( !ok ) + { + errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database." ); + return false; + } + } + + QString realStyleName = + styleName.isEmpty() ? QString( OGR_L_GetName( hUserLayer ) ) : styleName; + + OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer ); + + if ( useAsDefault ) + { + QString oldDefaultQuery = QStringLiteral( "useAsDefault = 1 AND f_table_schema=''" + " AND f_table_name=%1" + " AND f_geometry_column=%2" ) + .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetName( hUserLayer ) ) ) ) + .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetGeometryColumn( hUserLayer ) ) ) ); + OGR_L_SetAttributeFilter( hLayer, oldDefaultQuery.toUtf8().constData() ); + gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) ); + if ( hFeature ) + { + OGR_F_SetFieldInteger( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ), + 0 ); + bool ok = OGR_L_SetFeature( hLayer, hFeature.get() ) == 0; + if ( !ok ) + { + QgsDebugError( QStringLiteral( "Could not unset previous useAsDefault style" ) ); + } + } + } + + QString checkQuery = QStringLiteral( "f_table_schema=''" + " AND f_table_name=%1" + " AND f_geometry_column=%2" + " AND styleName=%3" ) + .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetName( hUserLayer ) ) ) ) + .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetGeometryColumn( hUserLayer ) ) ) ) + .arg( QgsOgrProviderUtils::quotedValue( realStyleName ) ); + OGR_L_SetAttributeFilter( hLayer, checkQuery.toUtf8().constData() ); + OGR_L_ResetReading( hLayer ); + gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) ); + OGR_L_ResetReading( hLayer ); + bool bNew = true; + + if ( hFeature ) + { + bNew = false; + } + else + { + hFeature.reset( OGR_F_Create( hLayerDefn ) ); + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "f_table_catalog" ), + "" ); + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "f_table_schema" ), + "" ); + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "f_table_name" ), + OGR_L_GetName( hUserLayer ) ); + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "f_geometry_column" ), + OGR_L_GetGeometryColumn( hUserLayer ) ); + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ), + realStyleName.toUtf8().constData() ); + if ( !uiFileContent.isEmpty() ) + { + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "ui" ), + uiFileContent.toUtf8().constData() ); + } + } + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ), + qmlStyle.toUtf8().constData() ); + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "styleSLD" ), + sldStyle.toUtf8().constData() ); + OGR_F_SetFieldInteger( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ), + useAsDefault ? 1 : 0 ); + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "description" ), + ( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ).toUtf8().constData() ); + OGR_F_SetFieldString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "owner" ), + "" ); + + bool bFeatureOK; + if ( bNew ) + bFeatureOK = OGR_L_CreateFeature( hLayer, hFeature.get() ) == OGRERR_NONE; + else + bFeatureOK = OGR_L_SetFeature( hLayer, hFeature.get() ) == OGRERR_NONE; + + if ( !bFeatureOK ) + { + QgsMessageLog::logMessage( QObject::tr( "Error updating style" ) ); + errCause = QObject::tr( "Error looking for style. The query was logged" ); + return false; + } + + return true; } bool QgsOgrProviderMetadata::deleteStyleById( const QString &uri, const QString &styleId, QString &errCause ) { - QString filePath; - QgsOgrLayerUniquePtr userLayer = LoadDataSourceAndLayer( uri, false, filePath, errCause ); + QgsDataSourceUri dsUri( uri ); + bool deleted; + + QgsOgrLayerUniquePtr userLayer = LoadDataSourceAndLayer( uri, errCause ); if ( !userLayer ) return false; + QRecursiveMutex *mutex = nullptr; GDALDatasetH hDS = userLayer->getDatasetHandleAndMutex( mutex ); QMutexLocker locker( mutex ); - return QgsOgrUtils::deleteStyleById( hDS, styleId, errCause ); + // check if layer_styles table already exist + OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" ); + if ( !hLayer ) + { + errCause = QObject::tr( "Connection to database failed: %1" ).arg( dsUri.uri() ); + deleted = false; + } + else + { + if ( OGR_L_DeleteFeature( hLayer, styleId.toInt() ) != OGRERR_NONE ) + { + errCause = QObject::tr( "Error executing the delete query." ); + deleted = false; + } + else + { + deleted = true; + } + } + return deleted; } +static +bool LoadDataSourceLayerStylesAndLayer( const QString &uri, + QgsOgrLayerUniquePtr &layerStyles, + QgsOgrLayerUniquePtr &userLayer, + QString &errCause ) +{ + bool isSubLayer; + int layerIndex; + QString layerName; + QString subsetString; + OGRwkbGeometryType ogrGeometryType; + QStringList openOptions; + QString filePath = QgsOgrProviderUtils::analyzeURI( uri, + isSubLayer, + layerIndex, + layerName, + subsetString, + ogrGeometryType, + openOptions ); + + layerStyles = + QgsOgrProviderUtils::getLayer( filePath, "layer_styles", errCause ); + userLayer = nullptr; + if ( !layerStyles ) + { + errCause = QObject::tr( "Cannot find layer_styles layer" ); + return false; + } + + if ( !layerName.isEmpty() ) + { + userLayer = QgsOgrProviderUtils::getLayer( filePath, layerName, errCause ); + } + else + { + userLayer = QgsOgrProviderUtils::getLayer( filePath, layerIndex, errCause ); + } + if ( !userLayer ) + { + layerStyles.reset(); + return false; + } + return true; +} + + QString QgsOgrProviderMetadata::loadStyle( const QString &uri, QString &errCause ) { QString name; @@ -436,53 +693,230 @@ QString QgsOgrProviderMetadata::loadStyle( const QString &uri, QString &errCause QString QgsOgrProviderMetadata::loadStoredStyle( const QString &uri, QString &styleName, QString &errCause ) { - QString filePath; - QgsOgrLayerUniquePtr userLayer = LoadDataSourceAndLayer( uri, false, filePath, errCause ); - if ( !userLayer ) + QgsOgrLayerUniquePtr layerStyles; + QgsOgrLayerUniquePtr userLayer; + if ( !LoadDataSourceLayerStylesAndLayer( uri, layerStyles, userLayer, errCause ) ) + { return QString(); + } - QRecursiveMutex *mutex = nullptr; - OGRLayerH hUserLayer = userLayer->getHandleAndMutex( mutex ); - GDALDatasetH hDS = userLayer->getDatasetHandleAndMutex( mutex ); - QMutexLocker lock( mutex ); - QString layerName = QString( OGR_L_GetName( hUserLayer ) ); - QString geomColumn = QString( OGR_L_GetGeometryColumn( hUserLayer ) ); + QRecursiveMutex *mutex1 = nullptr; + QRecursiveMutex *mutex2 = nullptr; - return QgsOgrUtils::loadStoredStyle( hDS, layerName, geomColumn, styleName, errCause ); + OGRLayerH hLayer = layerStyles->getHandleAndMutex( mutex1 ); + OGRLayerH hUserLayer = userLayer->getHandleAndMutex( mutex2 ); + QMutexLocker lock1( mutex1 ); + QMutexLocker lock2( mutex2 ); + + QString selectQmlQuery = QStringLiteral( "f_table_schema=''" + " AND f_table_name=%1" + " AND f_geometry_column=%2" + " ORDER BY CASE WHEN useAsDefault THEN 1 ELSE 2 END" + ",update_time DESC LIMIT 1" ) + .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetName( hUserLayer ) ) ) ) + .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetGeometryColumn( hUserLayer ) ) ) ); + OGR_L_SetAttributeFilter( hLayer, selectQmlQuery.toUtf8().constData() ); + OGR_L_ResetReading( hLayer ); + OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer ); + QString styleQML; + qlonglong moreRecentTimestamp = 0; + while ( true ) + { + gdal::ogr_feature_unique_ptr hFeat( OGR_L_GetNextFeature( hLayer ) ); + if ( !hFeat ) + break; + if ( OGR_F_GetFieldAsInteger( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ) ) ) + { + styleQML = QString::fromUtf8( + OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) ); + styleName = QString::fromUtf8( + OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) ); + break; + } + + int year, month, day, hour, minute, second, TZ; + OGR_F_GetFieldAsDateTime( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "update_time" ), + &year, &month, &day, &hour, &minute, &second, &TZ ); + qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 + + static_cast( month ) * 31 * 24 * 3600 + static_cast( year ) * 12 * 31 * 24 * 3600; + if ( ts > moreRecentTimestamp ) + { + moreRecentTimestamp = ts; + styleQML = QString::fromUtf8( + OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) ); + styleName = QString::fromUtf8( + OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) ); + } + } + OGR_L_ResetReading( hLayer ); + + return styleQML; } int QgsOgrProviderMetadata::listStyles( const QString &uri, QStringList &ids, QStringList &names, QStringList &descriptions, QString &errCause ) { - QString filePath; - QgsOgrLayerUniquePtr userLayer = LoadDataSourceAndLayer( uri, false, filePath, errCause ); + bool isSubLayer; + int layerIndex; + QString layerName; + QString subsetString; + OGRwkbGeometryType ogrGeometryType; + QStringList openOptions; + QString filePath = QgsOgrProviderUtils::analyzeURI( uri, + isSubLayer, + layerIndex, + layerName, + subsetString, + ogrGeometryType, + openOptions ); + + QgsOgrLayerUniquePtr userLayer; + if ( !layerName.isEmpty() ) + { + userLayer = QgsOgrProviderUtils::getLayer( filePath, layerName, errCause ); + } + else + { + userLayer = QgsOgrProviderUtils::getLayer( filePath, layerIndex, errCause ); + } if ( !userLayer ) + { return -1; + } - QRecursiveMutex *mutex = nullptr; - OGRLayerH hUserLayer = userLayer->getHandleAndMutex( mutex ); - GDALDatasetH hDS = userLayer->getDatasetHandleAndMutex( mutex ); - Q_UNUSED( hDS ); - QMutexLocker locker( mutex ); - QString layerName = QString( OGR_L_GetName( hUserLayer ) ); - QString geomColumn = QString( OGR_L_GetGeometryColumn( hUserLayer ) ); + QgsOgrLayerUniquePtr layerStyles = + QgsOgrProviderUtils::getLayer( filePath, "layer_styles", errCause ); + if ( !layerStyles ) + { + QgsMessageLog::logMessage( QObject::tr( "No styles available on DB" ) ); + errCause = QObject::tr( "No styles available on DB" ); + return 0; + } - return QgsOgrUtils::listStyles( hDS, layerName, geomColumn, ids, names, descriptions, errCause ); + QRecursiveMutex *mutex1 = nullptr; + QRecursiveMutex *mutex2 = nullptr; + + OGRLayerH hLayer = layerStyles->getHandleAndMutex( mutex1 ); + QMutexLocker lock1( mutex1 ); + OGRLayerH hUserLayer = userLayer->getHandleAndMutex( mutex2 ); + QMutexLocker lock2( mutex2 ); + + if ( OGR_L_GetFeatureCount( hLayer, TRUE ) == 0 ) + { + QgsMessageLog::logMessage( QObject::tr( "No styles available on DB" ) ); + errCause = QObject::tr( "No styles available on DB" ); + return 0; + } + + OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer ); + + OGR_L_ResetReading( hLayer ); + + QList listTimestamp; + QMap mapIdToStyleName; + QMap mapIdToDescription; + QMap > mapTimestampToId; + int numberOfRelatedStyles = 0; + + while ( true ) + { + gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) ); + if ( !hFeature ) + break; + + QString tableName( QString::fromUtf8( + OGR_F_GetFieldAsString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "f_table_name" ) ) ) ); + QString geometryColumn( QString::fromUtf8( + OGR_F_GetFieldAsString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "f_geometry_column" ) ) ) ); + QString styleName( QString::fromUtf8( + OGR_F_GetFieldAsString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) ) ); + QString description( QString::fromUtf8( + OGR_F_GetFieldAsString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "description" ) ) ) ); + int fid = static_cast( OGR_F_GetFID( hFeature.get() ) ); + if ( tableName == QString::fromUtf8( OGR_L_GetName( hUserLayer ) ) && + geometryColumn == QString::fromUtf8( OGR_L_GetGeometryColumn( hUserLayer ) ) ) + { + // Append first all related styles + QString id( QString::number( fid ) ); + ids.append( id ); + names.append( styleName ); + descriptions.append( description ); + ++ numberOfRelatedStyles; + } + else + { + int year, month, day, hour, minute, second, TZ; + OGR_F_GetFieldAsDateTime( hFeature.get(), OGR_FD_GetFieldIndex( hLayerDefn, "update_time" ), + &year, &month, &day, &hour, &minute, &second, &TZ ); + const qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 + + static_cast( month ) * 31 * 24 * 3600 + static_cast( year ) * 12 * 31 * 24 * 3600; + + listTimestamp.append( ts ); + mapIdToStyleName[fid] = styleName; + mapIdToDescription[fid] = description; + mapTimestampToId[ts].append( fid ); + } + } + + std::sort( listTimestamp.begin(), listTimestamp.end() ); + // Sort from most recent to least recent + for ( int i = listTimestamp.size() - 1; i >= 0; i-- ) + { + const QList &listId = mapTimestampToId[listTimestamp[i]]; + for ( int j = 0; j < listId.size(); j++ ) + { + int fid = listId[j]; + QString id( QString::number( fid ) ); + ids.append( id ); + names.append( mapIdToStyleName[fid] ); + descriptions.append( mapIdToDescription[fid] ); + } + } + + return numberOfRelatedStyles; } QString QgsOgrProviderMetadata::getStyleById( const QString &uri, const QString &styleId, QString &errCause ) { - QString filePath; - QgsOgrLayerUniquePtr userLayer = LoadDataSourceAndLayer( uri, false, filePath, errCause ); - if ( !userLayer ) + QgsOgrLayerUniquePtr layerStyles; + QgsOgrLayerUniquePtr userLayer; + if ( !LoadDataSourceLayerStylesAndLayer( uri, layerStyles, userLayer, errCause ) ) + { return QString(); + } - QRecursiveMutex *mutex = nullptr; - GDALDatasetH hDS = userLayer->getDatasetHandleAndMutex( mutex ); - QMutexLocker locker( mutex ); + QRecursiveMutex *mutex1 = nullptr; - return QgsOgrUtils::getStyleById( hDS, styleId, errCause ); + OGRLayerH hLayer = layerStyles->getHandleAndMutex( mutex1 ); + QMutexLocker lock1( mutex1 ); + + bool ok; + int id = styleId.toInt( &ok ); + if ( !ok ) + { + errCause = QObject::tr( "Invalid style identifier" ); + return QString(); + } + + gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetFeature( hLayer, id ) ); + if ( !hFeature ) + { + errCause = QObject::tr( "No style corresponding to style identifier" ); + return QString(); + } + + OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer ); + QString styleQML( QString::fromUtf8( + OGR_F_GetFieldAsString( hFeature.get(), + OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) ) ); + OGR_L_ResetReading( hLayer ); + + return styleQML; } bool QgsOgrProviderMetadata::saveLayerMetadata( const QString &uri, const QgsLayerMetadata &metadata, QString &errorMessage ) diff --git a/src/core/providers/qgsdataprovider.cpp b/src/core/providers/qgsdataprovider.cpp index 430f0fffe48..9f6956c6bd8 100644 --- a/src/core/providers/qgsdataprovider.cpp +++ b/src/core/providers/qgsdataprovider.cpp @@ -134,8 +134,3 @@ QString QgsDataProvider::sublayerSeparator() { return SUBLAYER_SEPARATOR; } - -Qgis::ProviderStyleStorageCapabilities QgsDataProvider::styleStorageCapabilities() const -{ - return Qgis::ProviderStyleStorageCapabilities(); -} diff --git a/src/core/providers/qgsdataprovider.h b/src/core/providers/qgsdataprovider.h index 6421dc33b54..946d707aced 100644 --- a/src/core/providers/qgsdataprovider.h +++ b/src/core/providers/qgsdataprovider.h @@ -633,13 +633,6 @@ class CORE_EXPORT QgsDataProvider : public QObject */ static QString sublayerSeparator(); - /** - * Returns the style storage capabilities. - * - * \since QGIS 3.34 - */ - virtual Qgis::ProviderStyleStorageCapabilities styleStorageCapabilities() const; - signals: /** diff --git a/src/core/qgis.h b/src/core/qgis.h index 775b420bbfc..641c73fb86a 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3690,21 +3690,6 @@ class CORE_EXPORT Qgis Q_DECLARE_FLAGS( DatabaseProviderConnectionCapabilities2, DatabaseProviderConnectionCapability2 ) Q_FLAG( DatabaseProviderConnectionCapabilities2 ) - /** - * The StorageCapability enum represents the style storage operations supported by the provider. - * - * \since QGIS 3.34 - */ - enum class ProviderStyleStorageCapability - { - SaveToDatabase = 1 << 1, - LoadFromDatabase = 1 << 2, - DeleteFromDatabase = 1 << 3 - }; - Q_ENUM( ProviderStyleStorageCapability ); - Q_DECLARE_FLAGS( ProviderStyleStorageCapabilities, ProviderStyleStorageCapability ) - Q_FLAG( ProviderStyleStorageCapabilities ) - /** * User profile selection policy. * diff --git a/src/core/qgsmaplayer.cpp b/src/core/qgsmaplayer.cpp index 96d0724cc90..0ab67386410 100644 --- a/src/core/qgsmaplayer.cpp +++ b/src/core/qgsmaplayer.cpp @@ -1365,7 +1365,7 @@ QString QgsMapLayer::loadNamedStyle( const QString &uri, bool &resultFlag, QgsMa { QGIS_PROTECT_QOBJECT_THREAD_ACCESS - return loadNamedStyle( uri, resultFlag, false, categories ); + return loadNamedProperty( uri, PropertyType::Style, resultFlag, categories ); } QString QgsMapLayer::loadNamedProperty( const QString &uri, QgsMapLayer::PropertyType type, bool &resultFlag, StyleCategories categories ) @@ -2350,90 +2350,6 @@ void QgsMapLayer::removeCustomProperty( const QString &key ) } } -int QgsMapLayer::listStylesInDatabase( QStringList &ids, QStringList &names, QStringList &descriptions, QString &msgError ) -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - return QgsProviderRegistry::instance()->listStyles( mProviderKey, mDataSource, ids, names, descriptions, msgError ); -} - -QString QgsMapLayer::getStyleFromDatabase( const QString &styleId, QString &msgError ) -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - return QgsProviderRegistry::instance()->getStyleById( mProviderKey, mDataSource, styleId, msgError ); -} - -bool QgsMapLayer::deleteStyleFromDatabase( const QString &styleId, QString &msgError ) -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - return QgsProviderRegistry::instance()->deleteStyleById( mProviderKey, mDataSource, styleId, msgError ); -} - -void QgsMapLayer::saveStyleToDatabase( const QString &name, const QString &description, - bool useAsDefault, const QString &uiFileContent, QString &msgError, QgsMapLayer::StyleCategories categories ) -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - QString sldStyle, qmlStyle; - QDomDocument qmlDocument, sldDocument; - QgsReadWriteContext context; - exportNamedStyle( qmlDocument, msgError, context, categories ); - if ( !msgError.isNull() ) - { - return; - } - qmlStyle = qmlDocument.toString(); - - this->exportSldStyle( sldDocument, msgError ); - if ( !msgError.isNull() ) - { - return; - } - sldStyle = sldDocument.toString(); - - QgsProviderRegistry::instance()->saveStyle( mProviderKey, - mDataSource, qmlStyle, sldStyle, name, - description, uiFileContent, useAsDefault, msgError ); -} - -QString QgsMapLayer::loadNamedStyle( const QString &theURI, bool &resultFlag, bool loadFromLocalDB, QgsMapLayer::StyleCategories categories ) -{ - QGIS_PROTECT_QOBJECT_THREAD_ACCESS - - QString returnMessage; - QString qml, errorMsg; - QString styleName; - if ( !loadFromLocalDB && dataProvider() && dataProvider()->styleStorageCapabilities().testFlag( Qgis::ProviderStyleStorageCapability::LoadFromDatabase ) ) - { - qml = QgsProviderRegistry::instance()->loadStoredStyle( mProviderKey, mDataSource, styleName, errorMsg ); - } - - // Style was successfully loaded from provider storage - if ( !qml.isEmpty() ) - { - QDomDocument myDocument( QStringLiteral( "qgis" ) ); - myDocument.setContent( qml ); - resultFlag = importNamedStyle( myDocument, errorMsg ); - returnMessage = QObject::tr( "Loaded from Provider" ); - } - else - { - returnMessage = loadNamedProperty( theURI, PropertyType::Style, resultFlag, categories ); - } - - if ( ! styleName.isEmpty() ) - { - styleManager()->renameStyle( styleManager()->currentStyle(), styleName ); - } - - if ( resultFlag ) - emit styleLoaded( categories ); - - return returnMessage; -} - QgsError QgsMapLayer::error() const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS diff --git a/src/core/qgsmaplayer.h b/src/core/qgsmaplayer.h index e1fe5ffef71..1b70ce30763 100644 --- a/src/core/qgsmaplayer.h +++ b/src/core/qgsmaplayer.h @@ -722,63 +722,6 @@ class CORE_EXPORT QgsMapLayer : public QObject */ const QgsObjectCustomProperties &customProperties() const; - /** - * Lists all the style in db split into related to the layer and not related to - * \param ids the list in which will be stored the style db ids - * \param names the list in which will be stored the style names - * \param descriptions the list in which will be stored the style descriptions - * \param msgError will be set to a descriptive error message if any occurs - * \returns the number of styles related to current layer (-1 on not implemented) - * \note Since QGIS 3.2 Styles related to the layer are ordered with the default style first then by update time for Postgres, MySQL and Spatialite. - */ - virtual int listStylesInDatabase( QStringList &ids SIP_OUT, QStringList &names SIP_OUT, - QStringList &descriptions SIP_OUT, QString &msgError SIP_OUT ); - - /** - * Returns the named style corresponding to style id provided - */ - virtual QString getStyleFromDatabase( const QString &styleId, QString &msgError SIP_OUT ); - - /** - * Deletes a style from the database - * \param styleId the provider's layer_styles table id of the style to delete - * \param msgError will be set to a descriptive error message if any occurs - * \returns TRUE in case of success - * \since QGIS 3.0 - */ - virtual bool deleteStyleFromDatabase( const QString &styleId, QString &msgError SIP_OUT ); - - /** - * Saves named and sld style of the layer to the style table in the db. - * \param name Style name - * \param description A description of the style - * \param useAsDefault Set to TRUE if style should be used as the default style for the layer - * \param uiFileContent - * \param msgError will be set to a descriptive error message if any occurs - * \param categories the style categories to be saved. - * - * - * \note Prior to QGIS 3.24, this method would show a message box warning when a - * style with the same \a styleName already existed to confirm replacing the style with the user. - * Since 3.24, calling this method will ALWAYS overwrite any existing style with the same name. - * Use QgsProviderRegistry::styleExists() to test in advance if a style already exists and handle this appropriately - * in your client code. - */ - virtual void saveStyleToDatabase( const QString &name, const QString &description, - bool useAsDefault, const QString &uiFileContent, - QString &msgError SIP_OUT, - QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ); - - /** - * Loads a named style from file/local db/datasource db - * \param theURI the URI of the style or the URI of the layer - * \param resultFlag will be set to TRUE if a named style is correctly loaded - * \param loadFromLocalDb if TRUE forces to load from local db instead of datasource one - * \param categories the style categories to be loaded. - */ - virtual QString loadNamedStyle( const QString &theURI, bool &resultFlag SIP_OUT, bool loadFromLocalDb, - QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ); - #ifndef SIP_RUN /** diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index 0458c76ab08..70be1a7686a 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -2666,428 +2666,3 @@ gdal::relationship_unique_ptr QgsOgrUtils::convertRelationship( const QgsWeakRel return relationH; } #endif - -int QgsOgrUtils::listStyles( GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, QStringList &ids, QStringList &names, QStringList &descriptions, QString &errCause ) -{ - OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" ); - if ( !hLayer ) - { - QgsMessageLog::logMessage( QObject::tr( "No styles available on DB" ) ); - errCause = QObject::tr( "No styles available on DB" ); - return 0; - } - - if ( OGR_L_GetFeatureCount( hLayer, TRUE ) == 0 ) - { - QgsMessageLog::logMessage( QObject::tr( "No styles available on DB" ) ); - errCause = QObject::tr( "No styles available on DB" ); - return 0; - } - - OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer ); - - OGR_L_ResetReading( hLayer ); - - QList listTimestamp; - QMap mapIdToStyleName; - QMap mapIdToDescription; - QMap > mapTimestampToId; - int numberOfRelatedStyles = 0; - - while ( true ) - { - gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) ); - if ( !hFeature ) - break; - - QString tableName( QString::fromUtf8( - OGR_F_GetFieldAsString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "f_table_name" ) ) ) ); - QString geometryColumn( QString::fromUtf8( - OGR_F_GetFieldAsString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "f_geometry_column" ) ) ) ); - QString styleName( QString::fromUtf8( - OGR_F_GetFieldAsString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) ) ); - QString description( QString::fromUtf8( - OGR_F_GetFieldAsString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "description" ) ) ) ); - int fid = static_cast( OGR_F_GetFID( hFeature.get() ) ); - if ( tableName == layerName && - geometryColumn == geomColumn ) - { - // Append first all related styles - QString id( QString::number( fid ) ); - ids.append( id ); - names.append( styleName ); - descriptions.append( description ); - ++ numberOfRelatedStyles; - } - else - { - int year, month, day, hour, minute, second, TZ; - OGR_F_GetFieldAsDateTime( hFeature.get(), OGR_FD_GetFieldIndex( hLayerDefn, "update_time" ), - &year, &month, &day, &hour, &minute, &second, &TZ ); - const qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 + - static_cast( month ) * 31 * 24 * 3600 + static_cast( year ) * 12 * 31 * 24 * 3600; - - listTimestamp.append( ts ); - mapIdToStyleName[fid] = styleName; - mapIdToDescription[fid] = description; - mapTimestampToId[ts].append( fid ); - } - } - - std::sort( listTimestamp.begin(), listTimestamp.end() ); - // Sort from most recent to least recent - for ( int i = listTimestamp.size() - 1; i >= 0; i-- ) - { - const QList &listId = mapTimestampToId[listTimestamp[i]]; - for ( int j = 0; j < listId.size(); j++ ) - { - int fid = listId[j]; - QString id( QString::number( fid ) ); - ids.append( id ); - names.append( mapIdToStyleName[fid] ); - descriptions.append( mapIdToDescription[fid] ); - } - } - - return numberOfRelatedStyles; -} - -bool QgsOgrUtils::styleExists( GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, const QString &styleId, QString &errorCause ) -{ - errorCause.clear(); - - // check if layer_styles table exists - OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" ); - if ( !hLayer ) - return false; - - const QString realStyleId = styleId.isEmpty() ? layerName : styleId; - - const QString checkQuery = QStringLiteral( "f_table_schema=''" - " AND f_table_name=%1" - " AND f_geometry_column=%2" - " AND styleName=%3" ) - .arg( QgsOgrProviderUtils::quotedValue( layerName ), - QgsOgrProviderUtils::quotedValue( geomColumn ), - QgsOgrProviderUtils::quotedValue( realStyleId ) ); - OGR_L_SetAttributeFilter( hLayer, checkQuery.toUtf8().constData() ); - OGR_L_ResetReading( hLayer ); - gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) ); - OGR_L_ResetReading( hLayer ); - - if ( hFeature ) - return true; - - return false; -} - -QString QgsOgrUtils::getStyleById( GDALDatasetH hDS, const QString &styleId, QString &errCause ) -{ - OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" ); - if ( !hLayer ) - { - QgsMessageLog::logMessage( QObject::tr( "No styles available on DB" ) ); - errCause = QObject::tr( "No styles available on DB" ); - return QString(); - } - - bool ok; - int id = styleId.toInt( &ok ); - if ( !ok ) - { - errCause = QObject::tr( "Invalid style identifier" ); - return QString(); - } - - gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetFeature( hLayer, id ) ); - if ( !hFeature ) - { - errCause = QObject::tr( "No style corresponding to style identifier" ); - return QString(); - } - - OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer ); - QString styleQML( QString::fromUtf8( - OGR_F_GetFieldAsString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) ) ); - OGR_L_ResetReading( hLayer ); - - return styleQML; -} - -bool QgsOgrUtils::deleteStyleById( GDALDatasetH hDS, const QString &styleId, QString &errCause ) -{ - bool deleted; - - OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" ); - - // check if layer_styles table already exist - if ( !hLayer ) - { - errCause = QObject::tr( "Connection to database failed" ); - deleted = false; - } - else - { - if ( OGR_L_DeleteFeature( hLayer, styleId.toInt() ) != OGRERR_NONE ) - { - errCause = QObject::tr( "Error executing the delete query." ); - deleted = false; - } - else - { - deleted = true; - } - } - return deleted; -} - -QString QgsOgrUtils::loadStoredStyle( GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, QString &styleName, QString &errCause ) -{ - OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" ); - if ( !hLayer ) - { - QgsMessageLog::logMessage( QObject::tr( "No styles available on DB" ) ); - errCause = QObject::tr( "No styles available on DB" ); - return QString(); - } - - QString selectQmlQuery = QStringLiteral( "f_table_schema=''" - " AND f_table_name=%1" - " AND f_geometry_column=%2" - " ORDER BY CASE WHEN useAsDefault THEN 1 ELSE 2 END" - ",update_time DESC LIMIT 1" ) - .arg( QgsOgrProviderUtils::quotedValue( layerName ) ) - .arg( QgsOgrProviderUtils::quotedValue( geomColumn ) ); - OGR_L_SetAttributeFilter( hLayer, selectQmlQuery.toUtf8().constData() ); - OGR_L_ResetReading( hLayer ); - OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer ); - QString styleQML; - qlonglong moreRecentTimestamp = 0; - while ( true ) - { - gdal::ogr_feature_unique_ptr hFeat( OGR_L_GetNextFeature( hLayer ) ); - if ( !hFeat ) - break; - if ( OGR_F_GetFieldAsInteger( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ) ) ) - { - styleQML = QString::fromUtf8( - OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) ); - styleName = QString::fromUtf8( - OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) ); - break; - } - - int year, month, day, hour, minute, second, TZ; - OGR_F_GetFieldAsDateTime( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "update_time" ), - &year, &month, &day, &hour, &minute, &second, &TZ ); - qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 + - static_cast( month ) * 31 * 24 * 3600 + static_cast( year ) * 12 * 31 * 24 * 3600; - if ( ts > moreRecentTimestamp ) - { - moreRecentTimestamp = ts; - styleQML = QString::fromUtf8( - OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) ); - styleName = QString::fromUtf8( - OGR_F_GetFieldAsString( hFeat.get(), OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) ); - } - } - OGR_L_ResetReading( hLayer ); - - return styleQML; -} - -bool QgsOgrUtils::saveStyle( - GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, const QString &qmlStyle, const QString &sldStyle, - const QString &styleName, const QString &styleDescription, - const QString &uiFileContent, bool useAsDefault, QString &errCause -) -{ - // check if layer_styles table already exist - OGRLayerH hLayer = GDALDatasetGetLayerByName( hDS, "layer_styles" ); - if ( !hLayer ) - { - // if not create it - // Note: we use the same schema as in the SpatiaLite and postgres providers - //for cross interoperability - - char **options = nullptr; - // TODO: might need change if other drivers than GPKG / SQLite - options = CSLSetNameValue( options, "FID", "id" ); - hLayer = GDALDatasetCreateLayer( hDS, "layer_styles", nullptr, wkbNone, options ); - QgsOgrProviderUtils::invalidateCachedDatasets( QString::fromUtf8( GDALGetDescription( hDS ) ) ); - CSLDestroy( options ); - if ( !hLayer ) - { - errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database." ); - return false; - } - bool ok = true; - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_table_catalog", OFTString ) ); - OGR_Fld_SetWidth( fld.get(), 256 ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_table_schema", OFTString ) ); - OGR_Fld_SetWidth( fld.get(), 256 ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_table_name", OFTString ) ); - OGR_Fld_SetWidth( fld.get(), 256 ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "f_geometry_column", OFTString ) ); - OGR_Fld_SetWidth( fld.get(), 256 ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "styleName", OFTString ) ); - OGR_Fld_SetWidth( fld.get(), 30 ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "styleQML", OFTString ) ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "styleSLD", OFTString ) ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "useAsDefault", OFTInteger ) ); - OGR_Fld_SetSubType( fld.get(), OFSTBoolean ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "description", OFTString ) ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "owner", OFTString ) ); - OGR_Fld_SetWidth( fld.get(), 30 ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "ui", OFTString ) ); - OGR_Fld_SetWidth( fld.get(), 30 ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - { - gdal::ogr_field_def_unique_ptr fld( OGR_Fld_Create( "update_time", OFTDateTime ) ); - OGR_Fld_SetDefault( fld.get(), "CURRENT_TIMESTAMP" ); - ok &= OGR_L_CreateField( hLayer, fld.get(), true ) == OGRERR_NONE; - } - if ( !ok ) - { - errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database." ); - return false; - } - } - - QString realStyleName = - styleName.isEmpty() ? layerName : styleName; - - OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer ); - - if ( useAsDefault ) - { - QString oldDefaultQuery = QStringLiteral( "useAsDefault = 1 AND f_table_schema=''" - " AND f_table_name=%1" - " AND f_geometry_column=%2" ) - .arg( QgsOgrProviderUtils::quotedValue( layerName ) ) - .arg( QgsOgrProviderUtils::quotedValue( geomColumn ) ); - OGR_L_SetAttributeFilter( hLayer, oldDefaultQuery.toUtf8().constData() ); - gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) ); - if ( hFeature ) - { - OGR_F_SetFieldInteger( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ), - 0 ); - bool ok = OGR_L_SetFeature( hLayer, hFeature.get() ) == 0; - if ( !ok ) - { - QgsDebugError( QStringLiteral( "Could not unset previous useAsDefault style" ) ); - } - } - } - - QString checkQuery = QStringLiteral( "f_table_schema=''" - " AND f_table_name=%1" - " AND f_geometry_column=%2" - " AND styleName=%3" ) - .arg( QgsOgrProviderUtils::quotedValue( layerName ) ) - .arg( QgsOgrProviderUtils::quotedValue( geomColumn ) ) - .arg( QgsOgrProviderUtils::quotedValue( realStyleName ) ); - OGR_L_SetAttributeFilter( hLayer, checkQuery.toUtf8().constData() ); - OGR_L_ResetReading( hLayer ); - gdal::ogr_feature_unique_ptr hFeature( OGR_L_GetNextFeature( hLayer ) ); - OGR_L_ResetReading( hLayer ); - bool bNew = true; - - if ( hFeature ) - { - bNew = false; - } - else - { - hFeature.reset( OGR_F_Create( hLayerDefn ) ); - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "f_table_catalog" ), - "" ); - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "f_table_schema" ), - "" ); - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "f_table_name" ), - layerName.toUtf8().constData() ); - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "f_geometry_column" ), - geomColumn.toUtf8().constData() ); - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ), - realStyleName.toUtf8().constData() ); - if ( !uiFileContent.isEmpty() ) - { - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "ui" ), - uiFileContent.toUtf8().constData() ); - } - } - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ), - qmlStyle.toUtf8().constData() ); - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "styleSLD" ), - sldStyle.toUtf8().constData() ); - OGR_F_SetFieldInteger( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ), - useAsDefault ? 1 : 0 ); - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "description" ), - ( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ).toUtf8().constData() ); - OGR_F_SetFieldString( hFeature.get(), - OGR_FD_GetFieldIndex( hLayerDefn, "owner" ), - "" ); - - bool bFeatureOK; - if ( bNew ) - bFeatureOK = OGR_L_CreateFeature( hLayer, hFeature.get() ) == OGRERR_NONE; - else - bFeatureOK = OGR_L_SetFeature( hLayer, hFeature.get() ) == OGRERR_NONE; - - if ( !bFeatureOK ) - { - QgsMessageLog::logMessage( QObject::tr( "Error updating style" ) ); - errCause = QObject::tr( "Error looking for style. The query was logged" ); - return false; - } - - return true; -} diff --git a/src/core/qgsogrutils.h b/src/core/qgsogrutils.h index e84686382a7..55918c9d071 100644 --- a/src/core/qgsogrutils.h +++ b/src/core/qgsogrutils.h @@ -484,53 +484,6 @@ class CORE_EXPORT QgsOgrUtils #endif #endif - /** - * Helper function for listing styles in ogr/gdal database datasources. - * - * \since QGIS 3.34 - */ - static int listStyles( GDALDatasetH hDS, const QString &layerName, - const QString &geomColumn, QStringList &ids, QStringList &names, - QStringList &descriptions, QString &errCause ); - - /** - * Helper function for checking whether a style exists in ogr/gdal database datasources. - * - * \since QGIS 3.34 - */ - static bool styleExists( GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, const QString &styleId, QString &errorCause ); - - /** - * Helper function for getting a style by ID from ogr/gdal database datasources. - * - * \since QGIS 3.34 - */ - static QString getStyleById( GDALDatasetH hDS, const QString &styleId, QString &errCause ); - - /** - * Helper function for saving a style to ogr/gdal database datasources. - * - * \since QGIS 3.34 - */ - static bool saveStyle( GDALDatasetH hDS, const QString &layerName, - const QString &geomColumn, const QString &qmlStyle, const QString &sldStyle, - const QString &styleName, const QString &styleDescription, - const QString &uiFileContent, bool useAsDefault, QString &errCause - ); - - /** - * Helper function for deleting a style by id from ogr/gdal database datasources. - * - * \since QGIS 3.34 - */ - static bool deleteStyleById( GDALDatasetH hDS, const QString &styleId, QString &errCause ); - - /** - * Helper function for loading a stored styles in ogr/gdal database datasources. - * - * \since QGIS 3.34 - */ - static QString loadStoredStyle( GDALDatasetH hDS, const QString &layerName, const QString &geomColumn, QString &styleName, QString &errCause ); }; #endif // QGSOGRUTILS_H diff --git a/src/core/vector/qgsvectordataprovider.cpp b/src/core/vector/qgsvectordataprovider.cpp index 804c4e6edeb..bad47cdfe94 100644 --- a/src/core/vector/qgsvectordataprovider.cpp +++ b/src/core/vector/qgsvectordataprovider.cpp @@ -850,6 +850,20 @@ QStringList QgsVectorDataProvider::errors() const return mErrors; } +bool QgsVectorDataProvider::isSaveAndLoadStyleToDatabaseSupported() const +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return false; +} + +bool QgsVectorDataProvider::isDeleteStyleFromDatabaseSupported() const +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return false; +} + QgsFeatureRenderer *QgsVectorDataProvider::createRenderer( const QVariantMap & ) const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS diff --git a/src/core/vector/qgsvectordataprovider.h b/src/core/vector/qgsvectordataprovider.h index efcdcaef05e..4d6727f7400 100644 --- a/src/core/vector/qgsvectordataprovider.h +++ b/src/core/vector/qgsvectordataprovider.h @@ -532,6 +532,18 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat */ QStringList errors() const; + /** + * It returns FALSE by default. + * Must be implemented by providers that support saving and loading styles to db returning TRUE + */ + virtual bool isSaveAndLoadStyleToDatabaseSupported() const; + + /** + * It returns FALSE by default. + * Must be implemented by providers that support delete styles from db returning TRUE + */ + virtual bool isDeleteStyleFromDatabaseSupported() const; + /** * Creates a new vector layer feature renderer, using provider backend specific information. * diff --git a/src/core/vector/qgsvectorlayer.cpp b/src/core/vector/qgsvectorlayer.cpp index afbe4e1d92c..faf959ccd21 100644 --- a/src/core/vector/qgsvectorlayer.cpp +++ b/src/core/vector/qgsvectorlayer.cpp @@ -1957,7 +1957,7 @@ QString QgsVectorLayer::loadDefaultStyle( bool &resultFlag ) if ( resultFlag ) { // Try to load all stored styles from DB - if ( mLoadAllStoredStyle && mDataProvider && mDataProvider->styleStorageCapabilities().testFlag( Qgis::ProviderStyleStorageCapability::LoadFromDatabase ) ) + if ( mLoadAllStoredStyle && mDataProvider && mDataProvider->isSaveAndLoadStyleToDatabaseSupported() ) { QStringList ids, names, descriptions; QString errorMessage; @@ -5793,6 +5793,61 @@ void QgsVectorLayer::setWeakRelations( const QList &relations ) mWeakRelations = relations; } +int QgsVectorLayer::listStylesInDatabase( QStringList &ids, QStringList &names, QStringList &descriptions, QString &msgError ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return QgsProviderRegistry::instance()->listStyles( mProviderKey, mDataSource, ids, names, descriptions, msgError ); +} + +QString QgsVectorLayer::getStyleFromDatabase( const QString &styleId, QString &msgError ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return QgsProviderRegistry::instance()->getStyleById( mProviderKey, mDataSource, styleId, msgError ); +} + +bool QgsVectorLayer::deleteStyleFromDatabase( const QString &styleId, QString &msgError ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return QgsProviderRegistry::instance()->deleteStyleById( mProviderKey, mDataSource, styleId, msgError ); +} + +void QgsVectorLayer::saveStyleToDatabase( const QString &name, const QString &description, + bool useAsDefault, const QString &uiFileContent, QString &msgError, QgsMapLayer::StyleCategories categories ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + QString sldStyle, qmlStyle; + QDomDocument qmlDocument, sldDocument; + QgsReadWriteContext context; + exportNamedStyle( qmlDocument, msgError, context, categories ); + if ( !msgError.isNull() ) + { + return; + } + qmlStyle = qmlDocument.toString(); + + this->exportSldStyle( sldDocument, msgError ); + if ( !msgError.isNull() ) + { + return; + } + sldStyle = sldDocument.toString(); + + QgsProviderRegistry::instance()->saveStyle( mProviderKey, + mDataSource, qmlStyle, sldStyle, name, + description, uiFileContent, useAsDefault, msgError ); +} + +QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &resultFlag, QgsMapLayer::StyleCategories categories ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + return loadNamedStyle( theURI, resultFlag, false, categories ); +} + bool QgsVectorLayer::loadAuxiliaryLayer( const QgsAuxiliaryStorage &storage, const QString &key ) { QGIS_PROTECT_QOBJECT_THREAD_ACCESS @@ -5863,6 +5918,43 @@ QgsAuxiliaryLayer *QgsVectorLayer::auxiliaryLayer() return mAuxiliaryLayer.get(); } +QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &resultFlag, bool loadFromLocalDB, QgsMapLayer::StyleCategories categories ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + QgsDataSourceUri dsUri( theURI ); + QString returnMessage; + QString qml, errorMsg; + QString styleName; + if ( !loadFromLocalDB && mDataProvider && mDataProvider->isSaveAndLoadStyleToDatabaseSupported() ) + { + qml = QgsProviderRegistry::instance()->loadStoredStyle( mProviderKey, mDataSource, styleName, errorMsg ); + } + + // Style was successfully loaded from provider storage + if ( !qml.isEmpty() ) + { + QDomDocument myDocument( QStringLiteral( "qgis" ) ); + myDocument.setContent( qml ); + resultFlag = importNamedStyle( myDocument, errorMsg ); + returnMessage = QObject::tr( "Loaded from Provider" ); + } + else + { + returnMessage = QgsMapLayer::loadNamedStyle( theURI, resultFlag, categories ); + } + + if ( ! styleName.isEmpty() ) + { + styleManager()->renameStyle( styleManager()->currentStyle(), styleName ); + } + + if ( resultFlag ) + emit styleLoaded( categories ); + + return returnMessage; +} + QSet QgsVectorLayer::dependencies() const { QGIS_PROTECT_QOBJECT_THREAD_ACCESS diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 8ee442c2d11..5491c9c2618 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -1023,6 +1023,70 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte */ void resolveReferences( QgsProject *project ) FINAL; + /** + * Saves named and sld style of the layer to the style table in the db. + * \param name Style name + * \param description A description of the style + * \param useAsDefault Set to TRUE if style should be used as the default style for the layer + * \param uiFileContent + * \param msgError will be set to a descriptive error message if any occurs + * \param categories the style categories to be saved. + * + * + * \note Prior to QGIS 3.24, this method would show a message box warning when a + * style with the same \a styleName already existed to confirm replacing the style with the user. + * Since 3.24, calling this method will ALWAYS overwrite any existing style with the same name. + * Use QgsProviderRegistry::styleExists() to test in advance if a style already exists and handle this appropriately + * in your client code. + */ + virtual void saveStyleToDatabase( const QString &name, const QString &description, + bool useAsDefault, const QString &uiFileContent, + QString &msgError SIP_OUT, + QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ); + + /** + * Lists all the style in db split into related to the layer and not related to + * \param ids the list in which will be stored the style db ids + * \param names the list in which will be stored the style names + * \param descriptions the list in which will be stored the style descriptions + * \param msgError will be set to a descriptive error message if any occurs + * \returns the number of styles related to current layer (-1 on not implemented) + * \note Since QGIS 3.2 Styles related to the layer are ordered with the default style first then by update time for Postgres, MySQL and Spatialite. + */ + virtual int listStylesInDatabase( QStringList &ids SIP_OUT, QStringList &names SIP_OUT, + QStringList &descriptions SIP_OUT, QString &msgError SIP_OUT ); + + /** + * Returns the named style corresponding to style id provided + */ + virtual QString getStyleFromDatabase( const QString &styleId, QString &msgError SIP_OUT ); + + /** + * Deletes a style from the database + * \param styleId the provider's layer_styles table id of the style to delete + * \param msgError will be set to a descriptive error message if any occurs + * \returns TRUE in case of success + * \since QGIS 3.0 + */ + virtual bool deleteStyleFromDatabase( const QString &styleId, QString &msgError SIP_OUT ); + + /** + * Loads a named style from file/local db/datasource db + * \param theURI the URI of the style or the URI of the layer + * \param resultFlag will be set to TRUE if a named style is correctly loaded + * \param loadFromLocalDb if TRUE forces to load from local db instead of datasource one + * \param categories the style categories to be loaded. + */ + virtual QString loadNamedStyle( const QString &theURI, bool &resultFlag SIP_OUT, bool loadFromLocalDb, + QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ); + + /** + * Calls loadNamedStyle( theURI, resultFlag, FALSE ); + * Retained for backward compatibility + */ + QString loadNamedStyle( const QString &theURI, bool &resultFlag SIP_OUT, + QgsMapLayer::StyleCategories categories = QgsMapLayer::AllStyleCategories ) FINAL; + /** * Loads the auxiliary layer for this vector layer. If there's no * corresponding table in the database, then nothing happens and FALSE is diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 239397baae5..016da19d4b8 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -39,6 +39,7 @@ set(QGIS_GUI_SRCS vector/qgssourcefieldsproperties.cpp vector/qgsvectorlayerlegendwidget.cpp vector/qgsvectorlayerproperties.cpp + vector/qgsvectorlayersavestyledialog.cpp vector/qgswmsdimensiondialog.cpp symbology/qgs25drendererwidget.cpp @@ -621,7 +622,6 @@ set(QGIS_GUI_SRCS qgsmaplayerconfigwidgetfactory.cpp qgsmaplayerloadstyledialog.cpp qgsmaplayerrefreshsettingswidget.cpp - qgsmaplayersavestyledialog.cpp qgsmaplayerstylecategoriesmodel.cpp qgsmaplayerstyleguiutils.cpp qgsmaplayerstylemanagerwidget.cpp @@ -897,7 +897,6 @@ set(QGIS_GUI_HDRS qgsmaplayercombobox.h qgsmaplayerconfigwidget.h qgsmaplayerconfigwidgetfactory.h - qgsmaplayersavestyledialog.h qgsmaplayerloadstyledialog.h qgsmaplayerrefreshsettingswidget.h qgsmaplayerstylecategoriesmodel.h @@ -1432,6 +1431,7 @@ set(QGIS_GUI_HDRS vector/qgssourcefieldsproperties.h vector/qgsvectorlayerlegendwidget.h vector/qgsvectorlayerproperties.h + vector/qgsvectorlayersavestyledialog.h vector/qgswmsdimensiondialog.h symbology/characterwidget.h diff --git a/src/gui/mesh/qgsmeshlayerproperties.h b/src/gui/mesh/qgsmeshlayerproperties.h index 3b831edf49f..e6f75be5961 100644 --- a/src/gui/mesh/qgsmeshlayerproperties.h +++ b/src/gui/mesh/qgsmeshlayerproperties.h @@ -24,6 +24,7 @@ class QgsMapLayer; class QgsMapCanvas; +class QgsMeshLayer; class QgsRendererMeshPropertiesWidget; class QgsMeshLayer3DRendererWidget; class QgsMeshStaticDatasetWidget; diff --git a/src/gui/qgslayerpropertiesdialog.cpp b/src/gui/qgslayerpropertiesdialog.cpp index 9773ec5dfe8..b3a091d3263 100644 --- a/src/gui/qgslayerpropertiesdialog.cpp +++ b/src/gui/qgslayerpropertiesdialog.cpp @@ -14,8 +14,6 @@ ***************************************************************************/ #include "qgslayerpropertiesdialog.h" -#include "qgsmaplayerloadstyledialog.h" -#include "qgsmaplayersavestyledialog.h" #include "qgsmaplayerconfigwidget.h" #include "qgsmaplayerconfigwidgetfactory.h" #include "qgsmaplayerstylemanager.h" @@ -23,9 +21,7 @@ #include "qgssettings.h" #include "qgsmaplayer.h" #include "qgsmetadatawidget.h" -#include "qgsproviderregistry.h" #include "qgsfileutils.h" -#include "qgssldexportcontext.h" #include "qstackedwidget.h" #include "qgsmapcanvas.h" @@ -39,6 +35,7 @@ QgsLayerPropertiesDialog::QgsLayerPropertiesDialog( QgsMapLayer *layer, QgsMapCa , mCanvas( canvas ) , mLayer( layer ) { + } void QgsLayerPropertiesDialog::setMetadataWidget( QgsMetadataWidget *widget, QWidget *page ) @@ -254,6 +251,29 @@ void QgsLayerPropertiesDialog::saveStyleAsDefault() } } +void QgsLayerPropertiesDialog::loadDefaultStyle() +{ + if ( !mLayer ) + return; + + bool defaultLoadedFlag = false; + const QString message = mLayer->loadDefaultStyle( defaultLoadedFlag ); + // reset if the default style was loaded OK only + if ( defaultLoadedFlag ) + { + syncToLayer(); + } + else + { + // otherwise let the user know what went wrong + QMessageBox::information( this, + tr( "Default Style" ), + message + ); + refocusDialog(); + } +} + void QgsLayerPropertiesDialog::initialize() { restoreOptionsBaseUi( generateDialogTitle() ); @@ -283,303 +303,6 @@ void QgsLayerPropertiesDialog::addPropertiesPageFactory( const QgsMapLayerConfig page->syncToLayer( mLayer ); } -void QgsLayerPropertiesDialog::loadDefaultStyle() -{ - QString msg; - bool defaultLoadedFlag = false; - - const QgsDataProvider *provider = mLayer->dataProvider(); - if ( !provider ) - return; - if ( provider->styleStorageCapabilities().testFlag( Qgis::ProviderStyleStorageCapability::LoadFromDatabase ) ) - { - QMessageBox askToUser; - askToUser.setText( tr( "Load default style from: " ) ); - askToUser.setIcon( QMessageBox::Question ); - askToUser.addButton( tr( "Cancel" ), QMessageBox::RejectRole ); - askToUser.addButton( tr( "Local Database" ), QMessageBox::NoRole ); - askToUser.addButton( tr( "Datasource Database" ), QMessageBox::YesRole ); - - switch ( askToUser.exec() ) - { - case 0: - return; - case 2: - msg = mLayer->loadNamedStyle( mLayer->styleURI(), defaultLoadedFlag, false ); - if ( !defaultLoadedFlag ) - { - //something went wrong - let them know why - QMessageBox::information( this, tr( "Default Style" ), msg ); - } - if ( msg.compare( tr( "Loaded from Provider" ) ) ) - { - QMessageBox::information( this, tr( "Default Style" ), - tr( "No default style was found for this layer." ) ); - } - else - { - syncToLayer(); - apply(); - } - - return; - default: - break; - } - } - - QString myMessage = mLayer->loadNamedStyle( mLayer->styleURI(), defaultLoadedFlag, true ); - // QString myMessage = layer->loadDefaultStyle( defaultLoadedFlag ); - //reset if the default style was loaded OK only - if ( defaultLoadedFlag ) - { - // all worked OK so no need to inform user - syncToLayer(); - apply(); - } - else - { - //something went wrong - let them know why - QMessageBox::information( this, tr( "Default Style" ), myMessage ); - } -} - -void QgsLayerPropertiesDialog::saveDefaultStyle() -{ - QString errorMsg; - const QgsDataProvider *provider = mLayer->dataProvider(); - if ( !provider ) - return; - if ( provider->styleStorageCapabilities().testFlag( Qgis::ProviderStyleStorageCapability::SaveToDatabase ) ) - { - QMessageBox askToUser; - askToUser.setText( tr( "Save default style to: " ) ); - askToUser.setIcon( QMessageBox::Question ); - askToUser.addButton( tr( "Cancel" ), QMessageBox::RejectRole ); - askToUser.addButton( tr( "Local Database" ), QMessageBox::NoRole ); - askToUser.addButton( tr( "Datasource Database" ), QMessageBox::YesRole ); - - switch ( askToUser.exec() ) - { - case 0: - return; - case 2: - { - apply(); - QString errorMessage; - if ( QgsProviderRegistry::instance()->styleExists( mLayer->providerType(), mLayer->source(), QString(), errorMessage ) ) - { - if ( QMessageBox::question( nullptr, QObject::tr( "Save style in database" ), - QObject::tr( "A matching style already exists in the database for this layer. Do you want to overwrite it?" ), - QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No ) - { - return; - } - } - else if ( !errorMessage.isEmpty() ) - { - QMessageBox::warning( nullptr, QObject::tr( "Save style in database" ), - errorMessage ); - return; - } - - mLayer->saveStyleToDatabase( QString(), QString(), true, QString(), errorMsg ); - if ( errorMsg.isNull() ) - { - return; - } - break; - } - default: - break; - } - } - - QgsLayerPropertiesDialog::saveStyleAsDefault(); -} - -void QgsLayerPropertiesDialog::saveStyleAs() -{ - if ( !mLayer->dataProvider() ) - return; - QgsMapLayerSaveStyleDialog dlg( mLayer ); - - if ( dlg.exec() ) - { - apply(); - - bool defaultLoadedFlag = false; - QString errorMessage; - - StyleType type = dlg.currentStyleType(); - switch ( type ) - { - case QML: - case SLD: - { - QString filePath = dlg.outputFilePath(); - if ( type == QML ) - errorMessage = mLayer->saveNamedStyle( filePath, defaultLoadedFlag, dlg.styleCategories() ); - else - { - const QgsSldExportContext sldContext { dlg.sldExportOptions(), Qgis::SldExportVendorExtension::NoVendorExtension, filePath }; - errorMessage = mLayer->saveSldStyleV2( defaultLoadedFlag, sldContext ); - } - - //reset if the default style was loaded OK only - if ( defaultLoadedFlag ) - { - syncToLayer(); - } - else - { - //let the user know what went wrong - QMessageBox::information( this, tr( "Save Style" ), errorMessage ); - } - - break; - } - case DatasourceDatabase: - { - QString infoWindowTitle = QObject::tr( "Save style to DB (%1)" ).arg( mLayer->providerType() ); - - QgsMapLayerSaveStyleDialog::SaveToDbSettings dbSettings = dlg.saveToDbSettings(); - - if ( QgsProviderRegistry::instance()->styleExists( mLayer->providerType(), mLayer->source(), dbSettings.name, errorMessage ) ) - { - if ( QMessageBox::question( nullptr, QObject::tr( "Save style in database" ), - QObject::tr( "A matching style already exists in the database for this layer. Do you want to overwrite it?" ), - QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No ) - { - return; - } - } - else if ( !errorMessage.isEmpty() ) - { - QMessageBox::warning( this, infoWindowTitle, errorMessage ); - return; - } - - mLayer->saveStyleToDatabase( dbSettings.name, dbSettings.description, dbSettings.isDefault, dbSettings.uiFileContent, errorMessage, dlg.styleCategories() ); - - if ( !errorMessage.isNull() ) - { - QMessageBox::warning( this, infoWindowTitle, errorMessage ); - } - else - { - QMessageBox::information( this, infoWindowTitle, tr( "Style saved" ) ); - } - break; - } - case UserDatabase: - { - QString infoWindowTitle = tr( "Save default style to local database" ); - errorMessage = mLayer->saveDefaultStyle( defaultLoadedFlag, dlg.styleCategories() ); - if ( !defaultLoadedFlag ) - { - QMessageBox::warning( this, infoWindowTitle, errorMessage ); - } - else - { - QMessageBox::information( this, infoWindowTitle, tr( "Style saved" ) ); - } - break; - } - } - } -} - -void QgsLayerPropertiesDialog::loadStyle() -{ - QString errorMsg; - QStringList ids, names, descriptions; - - //get the list of styles in the db - int sectionLimit = mLayer->listStylesInDatabase( ids, names, descriptions, errorMsg ); - QgsMapLayerLoadStyleDialog dlg( mLayer, this ); - dlg.initializeLists( ids, names, descriptions, sectionLimit ); - - if ( dlg.exec() ) - { - mOldStyle = mLayer->styleManager()->style( mLayer->styleManager()->currentStyle() ); - QgsMapLayer::StyleCategories categories = dlg.styleCategories(); - StyleType type = dlg.currentStyleType(); - bool defaultLoadedFlag = false; - switch ( type ) - { - case QML: - case SLD: - { - QString filePath = dlg.filePath(); - if ( type == SLD ) - { - errorMsg = mLayer->loadSldStyle( filePath, defaultLoadedFlag ); - } - else - { - errorMsg = mLayer->loadNamedStyle( filePath, defaultLoadedFlag, true, categories ); - } - //reset if the default style was loaded OK only - if ( defaultLoadedFlag ) - { - syncToLayer(); - apply(); - } - else - { - //let the user know what went wrong - QMessageBox::warning( this, tr( "Load Style" ), errorMsg ); - } - break; - } - case DatasourceDatabase: - { - QString selectedStyleId = dlg.selectedStyleId(); - - QString qmlStyle = mLayer->getStyleFromDatabase( selectedStyleId, errorMsg ); - if ( !errorMsg.isNull() ) - { - QMessageBox::warning( this, tr( "Load Styles from Database" ), errorMsg ); - return; - } - - QDomDocument myDocument( QStringLiteral( "qgis" ) ); - myDocument.setContent( qmlStyle ); - - if ( mLayer->importNamedStyle( myDocument, errorMsg, categories ) ) - { - syncToLayer(); - apply(); - } - else - { - QMessageBox::warning( this, tr( "Load Styles from Database" ), - tr( "The retrieved style is not a valid named style. Error message: %1" ) - .arg( errorMsg ) ); - } - break; - } - case UserDatabase: - { - errorMsg = mLayer->loadNamedStyle( mLayer->styleURI(), defaultLoadedFlag, true, categories ); - //reset if the default style was loaded OK only - if ( defaultLoadedFlag ) - { - syncToLayer(); - apply(); - } - else - { - QMessageBox::warning( this, tr( "Load Default Style" ), errorMsg ); - } - break; - } - } - activateWindow(); // set focus back to properties dialog - } -} - void QgsLayerPropertiesDialog::storeCurrentStyleForUndo() { if ( !mLayer ) diff --git a/src/gui/qgslayerpropertiesdialog.h b/src/gui/qgslayerpropertiesdialog.h index 2c47f009a58..aef6bb7439c 100644 --- a/src/gui/qgslayerpropertiesdialog.h +++ b/src/gui/qgslayerpropertiesdialog.h @@ -42,21 +42,6 @@ class GUI_EXPORT QgsLayerPropertiesDialog : public QgsOptionsDialogBase SIP_ABST public: -#ifndef SIP_RUN - - /** - * Style storage type. - */ - enum StyleType - { - QML, - SLD, - DatasourceDatabase, - UserDatabase, - }; - Q_ENUM( StyleType ) -#endif - /** * Constructor for QgsLayerPropertiesDialog. * @@ -81,27 +66,6 @@ class GUI_EXPORT QgsLayerPropertiesDialog : public QgsOptionsDialogBase SIP_ABST */ virtual void addPropertiesPageFactory( const QgsMapLayerConfigWidgetFactory *factory ); - /** - * Saves the default style when appropriate button is pressed - * - * \since QGIS 3.30 - */ - void saveDefaultStyle(); - - /** - * Triggers a dialog to load a saved style - * - * \since QGIS 3.30 - */ - void loadStyle(); - - /** - * Saves a style when appriate button is pressed - * - * \since QGIS 3.30 - */ - void saveStyleAs(); - public slots: /** diff --git a/src/gui/qgsmaplayerloadstyledialog.cpp b/src/gui/qgsmaplayerloadstyledialog.cpp index b800bb8f776..2a65f5c03d5 100644 --- a/src/gui/qgsmaplayerloadstyledialog.cpp +++ b/src/gui/qgsmaplayerloadstyledialog.cpp @@ -19,7 +19,7 @@ #include "qgsmaplayerloadstyledialog.h" #include "qgslogger.h" #include "qgssettings.h" -#include "qgslayerpropertiesdialog.h" +#include "qgsvectorlayerproperties.h" #include "qgsmaplayerstylecategoriesmodel.h" #include "qgshelp.h" #include "qgsapplication.h" @@ -43,29 +43,50 @@ QgsMapLayerLoadStyleDialog::QgsMapLayerLoadStyleDialog( QgsMapLayer *layer, QWid QgsSettings settings; + QString providerName = mLayer->providerType(); + if ( providerName == QLatin1String( "ogr" ) ) + { + QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( mLayer ); + providerName = vl->dataProvider()->storageType(); + if ( providerName == QLatin1String( "GPKG" ) ) + providerName = QStringLiteral( "GeoPackage" ); + } + const QString myLastUsedDir = settings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString(); // load style type combobox connect( mStyleTypeComboBox, qOverload( &QComboBox::currentIndexChanged ), this, [ = ]( int ) { - const QgsLayerPropertiesDialog::StyleType type = currentStyleType(); - mFileLabel->setVisible( type != QgsLayerPropertiesDialog::StyleType::DatasourceDatabase && type != QgsLayerPropertiesDialog::StyleType::UserDatabase ); - mFileWidget->setVisible( type != QgsLayerPropertiesDialog::StyleType::DatasourceDatabase && type != QgsLayerPropertiesDialog::StyleType::UserDatabase ); - mFromDbWidget->setVisible( type == QgsLayerPropertiesDialog::StyleType::DatasourceDatabase ); - mDeleteButton->setVisible( type == QgsLayerPropertiesDialog::StyleType::DatasourceDatabase && mLayer->dataProvider()->styleStorageCapabilities().testFlag( Qgis::ProviderStyleStorageCapability::DeleteFromDatabase ) ); + const QgsVectorLayerProperties::StyleType type = currentStyleType(); + QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( mLayer ); + mFileLabel->setVisible( !vl || ( type != QgsVectorLayerProperties::StyleType::DB && type != QgsVectorLayerProperties::StyleType::Local ) ); + mFileWidget->setVisible( !vl || ( type != QgsVectorLayerProperties::StyleType::DB && type != QgsVectorLayerProperties::StyleType::Local ) ); + if ( vl ) + { + mFromDbWidget->setVisible( type == QgsVectorLayerProperties::StyleType::DB ); + mDeleteButton->setVisible( type == QgsVectorLayerProperties::StyleType::DB && vl->dataProvider()->isDeleteStyleFromDatabaseSupported() ); + } + else + { + mFromDbWidget->setVisible( false ); + mDeleteButton->setVisible( false ); + } - mStyleCategoriesListView->setEnabled( currentStyleType() != QgsLayerPropertiesDialog::StyleType::SLD ); + mStyleCategoriesListView->setEnabled( !vl || currentStyleType() != QgsVectorLayerProperties::StyleType::SLD ); updateLoadButtonState(); } ); - mStyleTypeComboBox->addItem( tr( "From file" ), QgsLayerPropertiesDialog::QML ); // QML is used as entry, but works for SLD too, see currentStyleType() - mStyleTypeComboBox->addItem( tr( "Default from local database" ), QgsLayerPropertiesDialog::UserDatabase ); + mStyleTypeComboBox->addItem( tr( "From File" ), QgsVectorLayerProperties::QML ); // QML is used as entry, but works for SLD too, see currentStyleType() + mStyleTypeComboBox->addItem( tr( "Default from local database" ), QgsVectorLayerProperties::Local ); - if ( mLayer->dataProvider()->styleStorageCapabilities().testFlag( Qgis::ProviderStyleStorageCapability::LoadFromDatabase ) ) + if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( mLayer ) ) { - mStyleTypeComboBox->addItem( tr( "From datasource database" ), QgsLayerPropertiesDialog::StyleType::DatasourceDatabase ); - if ( settings.value( QStringLiteral( "style/lastLoadStyleTypeSelection" ) ) == QgsLayerPropertiesDialog::StyleType::DatasourceDatabase ) + if ( vl->dataProvider()->isSaveAndLoadStyleToDatabaseSupported() ) { - mStyleTypeComboBox->setCurrentIndex( mStyleTypeComboBox->findData( QgsLayerPropertiesDialog::StyleType::DatasourceDatabase ) ); + mStyleTypeComboBox->addItem( tr( "From Database (%1)" ).arg( providerName ), QgsVectorLayerProperties::StyleType::DB ); + if ( settings.value( QStringLiteral( "style/lastLoadStyleTypeSelection" ) ) == QgsVectorLayerProperties::StyleType::DB ) + { + mStyleTypeComboBox->setCurrentIndex( mStyleTypeComboBox->findData( QgsVectorLayerProperties::StyleType::DB ) ); + } } } @@ -103,7 +124,8 @@ QgsMapLayerLoadStyleDialog::QgsMapLayerLoadStyleDialog( QgsMapLayer *layer, QWid mFileWidget->setDefaultRoot( myLastUsedDir ); connect( mFileWidget, &QgsFileWidget::fileChanged, this, [ = ]( const QString & path ) { - mStyleCategoriesListView->setEnabled( currentStyleType() != QgsLayerPropertiesDialog::SLD ); + QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( mLayer ); + mStyleCategoriesListView->setEnabled( !vl || currentStyleType() != QgsVectorLayerProperties::SLD ); QgsSettings settings; const QFileInfo tmplFileInfo( path ); settings.setValue( QStringLiteral( "style/lastStyleDir" ), tmplFileInfo.absolutePath() ); @@ -145,14 +167,14 @@ QgsMapLayer::StyleCategories QgsMapLayerLoadStyleDialog::styleCategories() const return mModel->categories(); } -QgsLayerPropertiesDialog::StyleType QgsMapLayerLoadStyleDialog::currentStyleType() const +QgsVectorLayerProperties::StyleType QgsMapLayerLoadStyleDialog::currentStyleType() const { - QgsLayerPropertiesDialog::StyleType type = mStyleTypeComboBox->currentData().value(); - if ( type == QgsLayerPropertiesDialog::QML ) + QgsVectorLayerProperties::StyleType type = mStyleTypeComboBox->currentData().value(); + if ( type == QgsVectorLayerProperties::QML ) { const QFileInfo fi( mFileWidget->filePath() ); if ( fi.exists() && fi.suffix().compare( QStringLiteral( "sld" ), Qt::CaseInsensitive ) == 0 ) - type = QgsLayerPropertiesDialog::SLD; + type = QgsVectorLayerProperties::SLD; } return type; } @@ -276,6 +298,10 @@ void QgsMapLayerLoadStyleDialog::accept() void QgsMapLayerLoadStyleDialog::deleteStyleFromDB() { + QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( mLayer ); + if ( !vl ) + return; + QString msgError; const QString opInfo = QObject::tr( "Delete style %1 from %2" ).arg( mSelectedStyleName, mLayer->providerType() ); @@ -284,7 +310,7 @@ void QgsMapLayerLoadStyleDialog::deleteStyleFromDB() QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) return; - mLayer->deleteStyleFromDatabase( mSelectedStyleId, msgError ); + vl->deleteStyleFromDatabase( mSelectedStyleId, msgError ); if ( !msgError.isNull() ) { QgsDebugError( opInfo + " failed." ); @@ -302,7 +328,7 @@ void QgsMapLayerLoadStyleDialog::deleteStyleFromDB() QString errorMsg; QStringList ids, names, descriptions; //get the list of styles in the db - const int sectionLimit = mLayer->listStylesInDatabase( ids, names, descriptions, errorMsg ); + const int sectionLimit = vl->listStylesInDatabase( ids, names, descriptions, errorMsg ); if ( !errorMsg.isNull() ) { QMessageBox::warning( this, tr( "Error occurred while retrieving styles from database" ), errorMsg ); @@ -316,12 +342,19 @@ void QgsMapLayerLoadStyleDialog::deleteStyleFromDB() void QgsMapLayerLoadStyleDialog::updateLoadButtonState() { - const QgsLayerPropertiesDialog::StyleType type = currentStyleType(); - mLoadButton->setEnabled( ( type == QgsLayerPropertiesDialog::DatasourceDatabase - && ( mRelatedTable->selectionModel()->hasSelection() || mOthersTable->selectionModel()->hasSelection() - ) ) || - ( type != QgsLayerPropertiesDialog::DatasourceDatabase && !mFileWidget->filePath().isEmpty() ) || - type == QgsLayerPropertiesDialog::UserDatabase ); + const QgsVectorLayerProperties::StyleType type = currentStyleType(); + if ( mLayer->type() == Qgis::LayerType::Vector ) + { + mLoadButton->setEnabled( ( type == QgsVectorLayerProperties::DB + && ( mRelatedTable->selectionModel()->hasSelection() || mOthersTable->selectionModel()->hasSelection() + ) ) || + ( type != QgsVectorLayerProperties::DB && !mFileWidget->filePath().isEmpty() ) || + type == QgsVectorLayerProperties::Local ); + } + else + { + mLoadButton->setEnabled( !mFileWidget->filePath().isEmpty() ); + } } void QgsMapLayerLoadStyleDialog::showHelp() diff --git a/src/gui/qgsmaplayerloadstyledialog.h b/src/gui/qgsmaplayerloadstyledialog.h index 6e81f1e1579..fa8c10d5265 100644 --- a/src/gui/qgsmaplayerloadstyledialog.h +++ b/src/gui/qgsmaplayerloadstyledialog.h @@ -54,9 +54,9 @@ class GUI_EXPORT QgsMapLayerLoadStyleDialog : public QDialog, private Ui::QgsVec QgsMapLayer::StyleCategories styleCategories() const; /** - * Returns the selected style type. + * Returns the selected vector style type, for vector layers only. */ - QgsLayerPropertiesDialog::StyleType currentStyleType() const; + QgsVectorLayerProperties::StyleType currentStyleType() const; /** * Returns the file extension for the selected layer style source file. diff --git a/src/gui/qgsmaplayerstylecategoriesmodel.cpp b/src/gui/qgsmaplayerstylecategoriesmodel.cpp index 1df215f3b7f..e873833fc45 100644 --- a/src/gui/qgsmaplayerstylecategoriesmodel.cpp +++ b/src/gui/qgsmaplayerstylecategoriesmodel.cpp @@ -30,8 +30,6 @@ QgsMapLayerStyleCategoriesModel::QgsMapLayerStyleCategoriesModel( Qgis::LayerTyp break; case Qgis::LayerType::Raster: - mCategoryList << QgsMapLayer::StyleCategory::Symbology << QgsMapLayer::StyleCategory::AllStyleCategories; - break; case Qgis::LayerType::Annotation: case Qgis::LayerType::Plugin: case Qgis::LayerType::Mesh: @@ -43,11 +41,7 @@ QgsMapLayerStyleCategoriesModel::QgsMapLayerStyleCategoriesModel( Qgis::LayerTyp } // move All categories to top - int idxAllStyleCategories = mCategoryList.indexOf( QgsMapLayer::AllStyleCategories ); - if ( idxAllStyleCategories > 0 ) - { - mCategoryList.move( idxAllStyleCategories, 0 ); - } + mCategoryList.move( mCategoryList.indexOf( QgsMapLayer::AllStyleCategories ), 0 ); } void QgsMapLayerStyleCategoriesModel::setCategories( QgsMapLayer::StyleCategories categories ) @@ -75,7 +69,7 @@ void QgsMapLayerStyleCategoriesModel::setShowAllCategories( bool showAll ) int QgsMapLayerStyleCategoriesModel::rowCount( const QModelIndex & ) const { int count = mCategoryList.count(); - if ( count > 0 && !mShowAllCategories ) + if ( !mShowAllCategories ) count--; return count; } diff --git a/src/gui/raster/qgsrasterlayerproperties.cpp b/src/gui/raster/qgsrasterlayerproperties.cpp index 54a225cfe63..76bc5c82b23 100644 --- a/src/gui/raster/qgsrasterlayerproperties.cpp +++ b/src/gui/raster/qgsrasterlayerproperties.cpp @@ -527,7 +527,7 @@ QgsRasterLayerProperties::QgsRasterLayerProperties( QgsMapLayer *lyr, QgsMapCanv setMetadataWidget( mMetadataWidget, mOptsPage_Metadata ); QMenu *menuStyle = new QMenu( this ); - menuStyle->addAction( tr( "Load Style…" ), this, &QgsRasterLayerProperties::loadStyle ); + menuStyle->addAction( tr( "Load Style…" ), this, &QgsRasterLayerProperties::loadStyleFromFile ); menuStyle->addAction( tr( "Save Style…" ), this, &QgsRasterLayerProperties::saveStyleAs ); menuStyle->addSeparator(); menuStyle->addAction( tr( "Save as Default" ), this, &QgsRasterLayerProperties::saveStyleAsDefault ); @@ -1600,6 +1600,66 @@ void QgsRasterLayerProperties::saveDefaultStyle() saveStyleAsDefault(); } +void QgsRasterLayerProperties::loadStyle() +{ + loadStyleFromFile(); +} + +void QgsRasterLayerProperties::saveStyleAs() +{ + QgsSettings settings; + QString lastUsedDir = settings.value( QStringLiteral( "style/lastStyleDir" ), QDir::homePath() ).toString(); + + QString selectedFilter; + QString outputFileName = QFileDialog::getSaveFileName( + this, + tr( "Save layer properties as style file" ), + lastUsedDir, + tr( "QGIS Layer Style File" ) + " (*.qml)" + ";;" + tr( "Styled Layer Descriptor" ) + " (*.sld)", + &selectedFilter ); + if ( outputFileName.isEmpty() ) + return; + + StyleType type; + // use selectedFilter to set style type + if ( selectedFilter.contains( QStringLiteral( ".qml" ), Qt::CaseInsensitive ) ) + { + outputFileName = QgsFileUtils::ensureFileNameHasExtension( outputFileName, QStringList() << QStringLiteral( "qml" ) ); + type = StyleType::QML; + } + else + { + outputFileName = QgsFileUtils::ensureFileNameHasExtension( outputFileName, QStringList() << QStringLiteral( "sld" ) ); + type = StyleType::SLD; + } + + apply(); // make sure the style to save is up-to-date + + // then export style + bool defaultLoadedFlag = false; + QString message; + switch ( type ) + { + case QML: + { + message = mRasterLayer->saveNamedStyle( outputFileName, defaultLoadedFlag ); + break; + } + case SLD: + { + message = mRasterLayer->saveSldStyle( outputFileName, defaultLoadedFlag ); + break; + } + } + if ( defaultLoadedFlag ) + { + settings.setValue( QStringLiteral( "style/lastStyleDir" ), QFileInfo( outputFileName ).absolutePath() ); + sync(); + } + else + QMessageBox::information( this, tr( "Save Style" ), message ); +} + void QgsRasterLayerProperties::restoreWindowModality() { hide(); diff --git a/src/gui/raster/qgsrasterlayerproperties.h b/src/gui/raster/qgsrasterlayerproperties.h index 01195064ade..afac5e39f97 100644 --- a/src/gui/raster/qgsrasterlayerproperties.h +++ b/src/gui/raster/qgsrasterlayerproperties.h @@ -94,6 +94,20 @@ class GUI_EXPORT QgsRasterLayerProperties : public QgsLayerPropertiesDialog, pri */ Q_DECL_DEPRECATED void saveDefaultStyle() SIP_DEPRECATED; + /** + * Loads a saved style when appropriate button is pressed + * + * \deprecated use loadStyleFromFile() instead. + */ + Q_DECL_DEPRECATED void loadStyle() SIP_DEPRECATED; + + /** + * Saves a style when appriate button is pressed + * + * \since QGIS 3.30 + */ + void saveStyleAs(); + protected slots: void optionsStackedWidget_CurrentChanged( int index ) FINAL; void apply() FINAL; diff --git a/src/gui/vector/qgsvectorlayerproperties.cpp b/src/gui/vector/qgsvectorlayerproperties.cpp index 5f3798434c1..06395a1f160 100644 --- a/src/gui/vector/qgsvectorlayerproperties.cpp +++ b/src/gui/vector/qgsvectorlayerproperties.cpp @@ -48,11 +48,12 @@ #include "qgsrendererpropertiesdialog.h" #include "qgsstyle.h" #include "qgsauxiliarystorage.h" -#include "qgsmaplayersavestyledialog.h" #include "qgsmaplayerserverproperties.h" #include "qgsnewauxiliarylayerdialog.h" #include "qgsnewauxiliaryfielddialog.h" #include "qgslabelinggui.h" +#include "qgsvectorlayersavestyledialog.h" +#include "qgsmaplayerloadstyledialog.h" #include "qgsmessagebar.h" #include "qgssymbolwidgetcontext.h" #include "qgsexpressioncontextutils.h" @@ -1038,9 +1039,217 @@ void QgsVectorLayerProperties::mCrsSelector_crsChanged( const QgsCoordinateRefer mMetadataWidget->crsChanged(); } +void QgsVectorLayerProperties::loadDefaultStyle() +{ + QString msg; + bool defaultLoadedFlag = false; + + const QgsVectorDataProvider *provider = mLayer->dataProvider(); + if ( !provider ) + return; + if ( provider->isSaveAndLoadStyleToDatabaseSupported() ) + { + QMessageBox askToUser; + askToUser.setText( tr( "Load default style from: " ) ); + askToUser.setIcon( QMessageBox::Question ); + askToUser.addButton( tr( "Cancel" ), QMessageBox::RejectRole ); + askToUser.addButton( tr( "Local Database" ), QMessageBox::NoRole ); + askToUser.addButton( tr( "Datasource Database" ), QMessageBox::YesRole ); + + switch ( askToUser.exec() ) + { + case 0: + return; + case 2: + msg = mLayer->loadNamedStyle( mLayer->styleURI(), defaultLoadedFlag ); + if ( !defaultLoadedFlag ) + { + //something went wrong - let them know why + QMessageBox::information( this, tr( "Default Style" ), msg ); + } + if ( msg.compare( tr( "Loaded from Provider" ) ) ) + { + QMessageBox::information( this, tr( "Default Style" ), + tr( "No default style was found for this layer." ) ); + } + else + { + syncToLayer(); + apply(); + } + + return; + default: + break; + } + } + + QString myMessage = mLayer->loadNamedStyle( mLayer->styleURI(), defaultLoadedFlag, true ); +// QString myMessage = layer->loadDefaultStyle( defaultLoadedFlag ); + //reset if the default style was loaded OK only + if ( defaultLoadedFlag ) + { + // all worked OK so no need to inform user + syncToLayer(); + apply(); + } + else + { + //something went wrong - let them know why + QMessageBox::information( this, tr( "Default Style" ), myMessage ); + } +} + +void QgsVectorLayerProperties::saveDefaultStyle() +{ + QString errorMsg; + const QgsVectorDataProvider *provider = mLayer->dataProvider(); + if ( !provider ) + return; + if ( provider->isSaveAndLoadStyleToDatabaseSupported() ) + { + QMessageBox askToUser; + askToUser.setText( tr( "Save default style to: " ) ); + askToUser.setIcon( QMessageBox::Question ); + askToUser.addButton( tr( "Cancel" ), QMessageBox::RejectRole ); + askToUser.addButton( tr( "Local Database" ), QMessageBox::NoRole ); + askToUser.addButton( tr( "Datasource Database" ), QMessageBox::YesRole ); + + switch ( askToUser.exec() ) + { + case 0: + return; + case 2: + { + apply(); + QString errorMessage; + if ( QgsProviderRegistry::instance()->styleExists( mLayer->providerType(), mLayer->source(), QString(), errorMessage ) ) + { + if ( QMessageBox::question( nullptr, QObject::tr( "Save style in database" ), + QObject::tr( "A matching style already exists in the database for this layer. Do you want to overwrite it?" ), + QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No ) + { + return; + } + } + else if ( !errorMessage.isEmpty() ) + { + QMessageBox::warning( nullptr, QObject::tr( "Save style in database" ), + errorMessage ); + return; + } + + mLayer->saveStyleToDatabase( QString(), QString(), true, QString(), errorMsg ); + if ( errorMsg.isNull() ) + { + return; + } + break; + } + default: + break; + } + } + + QgsLayerPropertiesDialog::saveStyleAsDefault(); +} + +void QgsVectorLayerProperties::saveStyleAs() +{ + if ( !mLayer->dataProvider() ) + return; + QgsVectorLayerSaveStyleDialog dlg( mLayer ); + QgsSettings settings; + + if ( dlg.exec() ) + { + apply(); + + bool defaultLoadedFlag = false; + QString errorMessage; + + StyleType type = dlg.currentStyleType(); + switch ( type ) + { + case QML: + case SLD: + { + QString filePath = dlg.outputFilePath(); + if ( type == QML ) + errorMessage = mLayer->saveNamedStyle( filePath, defaultLoadedFlag, dlg.styleCategories() ); + else + { + const QgsSldExportContext sldContext { dlg.sldExportOptions(), Qgis::SldExportVendorExtension::NoVendorExtension, filePath }; + errorMessage = mLayer->saveSldStyleV2( defaultLoadedFlag, sldContext ); + } + + //reset if the default style was loaded OK only + if ( defaultLoadedFlag ) + { + syncToLayer(); + } + else + { + //let the user know what went wrong + QMessageBox::information( this, tr( "Save Style" ), errorMessage ); + } + + break; + } + case DB: + { + QString infoWindowTitle = QObject::tr( "Save style to DB (%1)" ).arg( mLayer->providerType() ); + + QgsVectorLayerSaveStyleDialog::SaveToDbSettings dbSettings = dlg.saveToDbSettings(); + + if ( QgsProviderRegistry::instance()->styleExists( mLayer->providerType(), mLayer->source(), dbSettings.name, errorMessage ) ) + { + if ( QMessageBox::question( nullptr, QObject::tr( "Save style in database" ), + QObject::tr( "A matching style already exists in the database for this layer. Do you want to overwrite it?" ), + QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No ) + { + return; + } + } + else if ( !errorMessage.isEmpty() ) + { + mMessageBar->pushMessage( infoWindowTitle, errorMessage, Qgis::MessageLevel::Warning ); + return; + } + + mLayer->saveStyleToDatabase( dbSettings.name, dbSettings.description, dbSettings.isDefault, dbSettings.uiFileContent, errorMessage, dlg.styleCategories() ); + + if ( !errorMessage.isNull() ) + { + mMessageBar->pushMessage( infoWindowTitle, errorMessage, Qgis::MessageLevel::Warning ); + } + else + { + mMessageBar->pushMessage( infoWindowTitle, tr( "Style saved" ), Qgis::MessageLevel::Success ); + } + break; + } + case Local: + { + QString infoWindowTitle = tr( "Save default style to local database" ); + errorMessage = mLayer->saveDefaultStyle( defaultLoadedFlag, dlg.styleCategories() ); + if ( !defaultLoadedFlag ) + { + mMessageBar->pushMessage( infoWindowTitle, errorMessage, Qgis::MessageLevel::Warning ); + } + else + { + mMessageBar->pushMessage( infoWindowTitle, tr( "Style saved" ), Qgis::MessageLevel::Success ); + } + break; + } + } + } +} + void QgsVectorLayerProperties::saveMultipleStylesAs() { - QgsMapLayerSaveStyleDialog dlg( mLayer ); + QgsVectorLayerSaveStyleDialog dlg( mLayer ); dlg.setSaveOnlyCurrentStyle( false ); QgsSettings settings; @@ -1110,13 +1319,13 @@ void QgsVectorLayerProperties::saveMultipleStylesAs() break; } - case DatasourceDatabase: + case DB: { QString infoWindowTitle = QObject::tr( "Save style '%1' to DB (%2)" ) .arg( styleName, mLayer->providerType() ); QString msgError; - QgsMapLayerSaveStyleDialog::SaveToDbSettings dbSettings = dlg.saveToDbSettings(); + QgsVectorLayerSaveStyleDialog::SaveToDbSettings dbSettings = dlg.saveToDbSettings(); // If a name is defined, we add _1 etc. else we use the style name QString name { dbSettings.name }; @@ -1149,7 +1358,7 @@ void QgsVectorLayerProperties::saveMultipleStylesAs() } else if ( !errorMessage.isEmpty() ) { - QMessageBox::warning( this, infoWindowTitle, errorMessage ); + mMessageBar->pushMessage( infoWindowTitle, errorMessage, Qgis::MessageLevel::Warning ); return; } @@ -1157,15 +1366,16 @@ void QgsVectorLayerProperties::saveMultipleStylesAs() if ( !msgError.isNull() ) { - QMessageBox::warning( this, infoWindowTitle, msgError ); + mMessageBar->pushMessage( infoWindowTitle, msgError, Qgis::MessageLevel::Warning ); } else { - QMessageBox::information( this, infoWindowTitle, tr( "Style '%1' saved" ).arg( styleName ) ); + mMessageBar->pushMessage( infoWindowTitle, tr( "Style '%1' saved" ).arg( styleName ), + Qgis::MessageLevel::Success ); } break; } - case UserDatabase: + case Local: break; } styleIndex ++; @@ -1205,6 +1415,98 @@ void QgsVectorLayerProperties::aboutToShowStyleMenu() QgsMapLayerStyleGuiUtils::instance()->addStyleManagerActions( m, mLayer ); } +void QgsVectorLayerProperties::loadStyle() +{ + QgsSettings settings; // where we keep last used filter in persistent state + + QString errorMsg; + QStringList ids, names, descriptions; + + //get the list of styles in the db + int sectionLimit = mLayer->listStylesInDatabase( ids, names, descriptions, errorMsg ); + QgsMapLayerLoadStyleDialog dlg( mLayer, this ); + dlg.initializeLists( ids, names, descriptions, sectionLimit ); + + if ( dlg.exec() ) + { + mOldStyle = mLayer->styleManager()->style( mLayer->styleManager()->currentStyle() ); + QgsMapLayer::StyleCategories categories = dlg.styleCategories(); + StyleType type = dlg.currentStyleType(); + bool defaultLoadedFlag = false; + switch ( type ) + { + case QML: + case SLD: + { + QString filePath = dlg.filePath(); + if ( type == SLD ) + { + errorMsg = mLayer->loadSldStyle( filePath, defaultLoadedFlag ); + } + else + { + errorMsg = mLayer->loadNamedStyle( filePath, defaultLoadedFlag, true, categories ); + } + //reset if the default style was loaded OK only + if ( defaultLoadedFlag ) + { + syncToLayer(); + apply(); + } + else + { + //let the user know what went wrong + QMessageBox::warning( this, tr( "Load Style" ), errorMsg ); + } + break; + } + case DB: + { + QString selectedStyleId = dlg.selectedStyleId(); + + QString qmlStyle = mLayer->getStyleFromDatabase( selectedStyleId, errorMsg ); + if ( !errorMsg.isNull() ) + { + QMessageBox::warning( this, tr( "Load Styles from Database" ), errorMsg ); + return; + } + + QDomDocument myDocument( QStringLiteral( "qgis" ) ); + myDocument.setContent( qmlStyle ); + + if ( mLayer->importNamedStyle( myDocument, errorMsg, categories ) ) + { + syncToLayer(); + apply(); + } + else + { + QMessageBox::warning( this, tr( "Load Styles from Database" ), + tr( "The retrieved style is not a valid named style. Error message: %1" ) + .arg( errorMsg ) ); + } + break; + } + case Local: + { + errorMsg = mLayer->loadNamedStyle( mLayer->styleURI(), defaultLoadedFlag, true, categories ); + //reset if the default style was loaded OK only + if ( defaultLoadedFlag ) + { + syncToLayer(); + apply(); + } + else + { + QMessageBox::warning( this, tr( "Load Default Style" ), errorMsg ); + } + break; + } + } + activateWindow(); // set focus back to properties dialog + } +} + void QgsVectorLayerProperties::mButtonAddJoin_clicked() { if ( !mLayer ) diff --git a/src/gui/vector/qgsvectorlayerproperties.h b/src/gui/vector/qgsvectorlayerproperties.h index 65f87448745..b0144ae6768 100644 --- a/src/gui/vector/qgsvectorlayerproperties.h +++ b/src/gui/vector/qgsvectorlayerproperties.h @@ -59,11 +59,49 @@ class GUI_EXPORT QgsVectorLayerProperties : public QgsLayerPropertiesDialog, pri Q_OBJECT public: +#ifndef SIP_RUN + enum StyleType + { + QML, + SLD, + DB, + Local, + }; + Q_ENUM( StyleType ) +#endif QgsVectorLayerProperties( QgsMapCanvas *canvas, QgsMessageBar *messageBar, QgsVectorLayer *lyr = nullptr, QWidget *parent = nullptr, Qt::WindowFlags fl = QgsGuiUtils::ModalDialogFlags ); bool eventFilter( QObject *obj, QEvent *ev ) override; + /** + * Loads the default style when appropriate button is pressed + * + * \since QGIS 3.30 + */ + void loadDefaultStyle(); + + /** + * Saves the default style when appropriate button is pressed + * + * \since QGIS 3.30 + */ + void saveDefaultStyle(); + + /** + * Loads a saved style when appropriate button is pressed + * + * \since QGIS 3.30 + */ + void loadStyle(); + + /** + * Saves a style when appriate button is pressed + * + * \since QGIS 3.30 + */ + void saveStyleAs(); + protected slots: void optionsStackedWidget_CurrentChanged( int index ) final; void syncToLayer() FINAL; diff --git a/src/gui/qgsmaplayersavestyledialog.cpp b/src/gui/vector/qgsvectorlayersavestyledialog.cpp similarity index 66% rename from src/gui/qgsmaplayersavestyledialog.cpp rename to src/gui/vector/qgsvectorlayersavestyledialog.cpp index f0194e8b7f4..b99d240c995 100644 --- a/src/gui/qgsmaplayersavestyledialog.cpp +++ b/src/gui/vector/qgsvectorlayersavestyledialog.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - qgsmaplayersavestyledialog.h + qgsvectorlayersavestyledialog.h -------------------------------------- Date : September 2018 Copyright : (C) 2018 by Denis Rouzaud @@ -15,17 +15,16 @@ #include #include -#include -#include "qgsmaplayersavestyledialog.h" +#include "qgsvectorlayersavestyledialog.h" +#include "qgsvectorlayer.h" #include "qgssettings.h" #include "qgshelp.h" #include "qgsgui.h" #include "qgsmaplayerstylecategoriesmodel.h" #include "qgsmaplayerstylemanager.h" -#include "qgsvectorlayer.h" -QgsMapLayerSaveStyleDialog::QgsMapLayerSaveStyleDialog( QgsMapLayer *layer, QWidget *parent ) +QgsVectorLayerSaveStyleDialog::QgsVectorLayerSaveStyleDialog( QgsVectorLayer *layer, QWidget *parent ) : QDialog( parent ) , mLayer( layer ) { @@ -39,28 +38,28 @@ QgsMapLayerSaveStyleDialog::QgsMapLayerSaveStyleDialog( QgsMapLayer *layer, QWid // save style type combobox connect( mStyleTypeComboBox, qOverload( &QComboBox::currentIndexChanged ), this, [ = ]( int ) { - const QgsLayerPropertiesDialog::StyleType type = currentStyleType(); - mFileLabel->setVisible( type != QgsLayerPropertiesDialog::DatasourceDatabase && type != QgsLayerPropertiesDialog::UserDatabase ); - mFileWidget->setVisible( type != QgsLayerPropertiesDialog::DatasourceDatabase && type != QgsLayerPropertiesDialog::UserDatabase ); - mSaveToDbWidget->setVisible( type == QgsLayerPropertiesDialog::DatasourceDatabase ); - mSaveToSldWidget->setVisible( type == QgsLayerPropertiesDialog::SLD && layer->type() == Qgis::LayerType::Vector && static_cast( layer )->geometryType() == Qgis::GeometryType::Polygon ); - mStyleCategoriesListView->setEnabled( type != QgsLayerPropertiesDialog::SLD ); - mFileWidget->setFilter( type == QgsLayerPropertiesDialog::QML ? tr( "QGIS Layer Style File (*.qml)" ) : tr( "SLD File (*.sld)" ) ); + const QgsVectorLayerProperties::StyleType type = currentStyleType(); + mFileLabel->setVisible( type != QgsVectorLayerProperties::DB && type != QgsVectorLayerProperties::Local ); + mFileWidget->setVisible( type != QgsVectorLayerProperties::DB && type != QgsVectorLayerProperties::Local ); + mSaveToDbWidget->setVisible( type == QgsVectorLayerProperties::DB ); + mSaveToSldWidget->setVisible( type == QgsVectorLayerProperties::SLD && layer->geometryType() == Qgis::GeometryType::Polygon ); + mStyleCategoriesListView->setEnabled( type != QgsVectorLayerProperties::SLD ); + mFileWidget->setFilter( type == QgsVectorLayerProperties::QML ? tr( "QGIS Layer Style File (*.qml)" ) : tr( "SLD File (*.sld)" ) ); updateSaveButtonState(); } ); // Save to DB setup - connect( mDbStyleNameEdit, &QLineEdit::textChanged, this, &QgsMapLayerSaveStyleDialog::updateSaveButtonState ); + connect( mDbStyleNameEdit, &QLineEdit::textChanged, this, &QgsVectorLayerSaveStyleDialog::updateSaveButtonState ); mDbStyleDescriptionEdit->setTabChangesFocus( true ); setTabOrder( mDbStyleNameEdit, mDbStyleDescriptionEdit ); setTabOrder( mDbStyleDescriptionEdit, mDbStyleUseAsDefault ); mDbStyleUIFileWidget->setDefaultRoot( myLastUsedDir ); mDbStyleUIFileWidget->setFilter( tr( "Qt Designer UI file (*.ui)" ) ); - connect( mDbStyleUIFileWidget, &QgsFileWidget::fileChanged, this, &QgsMapLayerSaveStyleDialog::readUiFileContent ); - connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsMapLayerSaveStyleDialog::showHelp ); + connect( mDbStyleUIFileWidget, &QgsFileWidget::fileChanged, this, &QgsVectorLayerSaveStyleDialog::readUiFileContent ); + connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsVectorLayerSaveStyleDialog::showHelp ); // save to file setup - connect( mFileWidget, &QgsFileWidget::fileChanged, this, &QgsMapLayerSaveStyleDialog::updateSaveButtonState ); + connect( mFileWidget, &QgsFileWidget::fileChanged, this, &QgsVectorLayerSaveStyleDialog::updateSaveButtonState ); mFileWidget->setStorageMode( QgsFileWidget::SaveFile ); mFileWidget->setDefaultRoot( myLastUsedDir ); connect( mFileWidget, &QgsFileWidget::fileChanged, this, [ = ]( const QString & path ) @@ -82,32 +81,40 @@ QgsMapLayerSaveStyleDialog::QgsMapLayerSaveStyleDialog( QgsMapLayer *layer, QWid } -void QgsMapLayerSaveStyleDialog::populateStyleComboBox() +void QgsVectorLayerSaveStyleDialog::populateStyleComboBox() { - mStyleTypeComboBox->clear(); - mStyleTypeComboBox->addItem( tr( "As QGIS QML style file" ), QgsLayerPropertiesDialog::QML ); - mStyleTypeComboBox->addItem( tr( "As SLD style file" ), QgsLayerPropertiesDialog::SLD ); + QString providerName = mLayer->providerType(); + if ( providerName == QLatin1String( "ogr" ) ) + { + providerName = mLayer->dataProvider()->storageType(); + if ( providerName == QLatin1String( "GPKG" ) ) + providerName = QStringLiteral( "GeoPackage" ); + } - if ( mLayer->dataProvider()->styleStorageCapabilities().testFlag( Qgis::ProviderStyleStorageCapability::SaveToDatabase ) ) - mStyleTypeComboBox->addItem( tr( "In datasource database" ), QgsLayerPropertiesDialog::DatasourceDatabase ); + mStyleTypeComboBox->clear(); + mStyleTypeComboBox->addItem( tr( "As QGIS QML Style File" ), QgsVectorLayerProperties::QML ); + mStyleTypeComboBox->addItem( tr( "As SLD Style File" ), QgsVectorLayerProperties::SLD ); + + if ( mLayer->dataProvider()->isSaveAndLoadStyleToDatabaseSupported() ) + mStyleTypeComboBox->addItem( tr( "In Database (%1)" ).arg( providerName ), QgsVectorLayerProperties::DB ); if ( mSaveOnlyCurrentStyle ) - mStyleTypeComboBox->addItem( tr( "As default in local user database" ), QgsLayerPropertiesDialog::UserDatabase ); + mStyleTypeComboBox->addItem( tr( "As Default In Local Database" ), QgsVectorLayerProperties::Local ); } -void QgsMapLayerSaveStyleDialog::accept() +void QgsVectorLayerSaveStyleDialog::accept() { QgsSettings().setFlagValue( QStringLiteral( "style/lastStyleCategories" ), styleCategories() ); QDialog::accept(); } -void QgsMapLayerSaveStyleDialog::updateSaveButtonState() +void QgsVectorLayerSaveStyleDialog::updateSaveButtonState() { - const QgsLayerPropertiesDialog::StyleType type = currentStyleType(); + const QgsVectorLayerProperties::StyleType type = currentStyleType(); bool enabled { false }; switch ( type ) { - case QgsLayerPropertiesDialog::DatasourceDatabase: + case QgsVectorLayerProperties::DB: if ( saveOnlyCurrentStyle( ) ) { enabled = ! mDbStyleNameEdit->text().isEmpty(); @@ -117,18 +124,18 @@ void QgsMapLayerSaveStyleDialog::updateSaveButtonState() enabled = true; } break; - case QgsLayerPropertiesDialog::QML: - case QgsLayerPropertiesDialog::SLD: + case QgsVectorLayerProperties::QML: + case QgsVectorLayerProperties::SLD: enabled = ! mFileWidget->filePath().isEmpty(); break; - case QgsLayerPropertiesDialog::UserDatabase: + case QgsVectorLayerProperties::Local: enabled = true; break; } buttonBox->button( QDialogButtonBox::Ok )->setEnabled( enabled ); } -QgsMapLayerSaveStyleDialog::SaveToDbSettings QgsMapLayerSaveStyleDialog::saveToDbSettings() const +QgsVectorLayerSaveStyleDialog::SaveToDbSettings QgsVectorLayerSaveStyleDialog::saveToDbSettings() const { SaveToDbSettings settings; settings.name = mDbStyleNameEdit->text(); @@ -138,22 +145,22 @@ QgsMapLayerSaveStyleDialog::SaveToDbSettings QgsMapLayerSaveStyleDialog::saveToD return settings; } -QString QgsMapLayerSaveStyleDialog::outputFilePath() const +QString QgsVectorLayerSaveStyleDialog::outputFilePath() const { return mFileWidget->filePath(); } -QgsMapLayer::StyleCategories QgsMapLayerSaveStyleDialog::styleCategories() const +QgsMapLayer::StyleCategories QgsVectorLayerSaveStyleDialog::styleCategories() const { return mModel->categories(); } -QgsLayerPropertiesDialog::StyleType QgsMapLayerSaveStyleDialog::currentStyleType() const +QgsVectorLayerProperties::StyleType QgsVectorLayerSaveStyleDialog::currentStyleType() const { - return mStyleTypeComboBox->currentData().value(); + return mStyleTypeComboBox->currentData().value(); } -void QgsMapLayerSaveStyleDialog::readUiFileContent( const QString &filePath ) +void QgsVectorLayerSaveStyleDialog::readUiFileContent( const QString &filePath ) { QgsSettings myQSettings; // where we keep last used filter in persistent state mUiFileContent = QString(); @@ -184,7 +191,7 @@ void QgsMapLayerSaveStyleDialog::readUiFileContent( const QString &filePath ) } } -void QgsMapLayerSaveStyleDialog::setupMultipleStyles() +void QgsVectorLayerSaveStyleDialog::setupMultipleStyles() { // Show/hide part of the UI according to multiple style support if ( ! mSaveOnlyCurrentStyle ) @@ -222,12 +229,12 @@ void QgsMapLayerSaveStyleDialog::setupMultipleStyles() populateStyleComboBox(); } -bool QgsMapLayerSaveStyleDialog::saveOnlyCurrentStyle() const +bool QgsVectorLayerSaveStyleDialog::saveOnlyCurrentStyle() const { return mSaveOnlyCurrentStyle; } -void QgsMapLayerSaveStyleDialog::setSaveOnlyCurrentStyle( bool saveOnlyCurrentStyle ) +void QgsVectorLayerSaveStyleDialog::setSaveOnlyCurrentStyle( bool saveOnlyCurrentStyle ) { if ( mSaveOnlyCurrentStyle != saveOnlyCurrentStyle ) { @@ -236,23 +243,23 @@ void QgsMapLayerSaveStyleDialog::setSaveOnlyCurrentStyle( bool saveOnlyCurrentSt } } -const QListWidget *QgsMapLayerSaveStyleDialog::stylesWidget() +const QListWidget *QgsVectorLayerSaveStyleDialog::stylesWidget() { return mStylesWidget; } -Qgis::SldExportOptions QgsMapLayerSaveStyleDialog::sldExportOptions() const +Qgis::SldExportOptions QgsVectorLayerSaveStyleDialog::sldExportOptions() const { Qgis::SldExportOptions options; - if ( mStyleTypeComboBox->currentData( ) == QgsLayerPropertiesDialog::SLD ) + if ( mStyleTypeComboBox->currentData( ) == QgsVectorLayerProperties::SLD ) { options.setFlag( Qgis::SldExportOption::Png ); } return options; } -void QgsMapLayerSaveStyleDialog::showHelp() +void QgsVectorLayerSaveStyleDialog::showHelp() { QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#save-and-share-layer-properties" ) ); } diff --git a/src/gui/qgsmaplayersavestyledialog.h b/src/gui/vector/qgsvectorlayersavestyledialog.h similarity index 62% rename from src/gui/qgsmaplayersavestyledialog.h rename to src/gui/vector/qgsvectorlayersavestyledialog.h index fbba94bcdc0..39724768b54 100644 --- a/src/gui/qgsmaplayersavestyledialog.h +++ b/src/gui/vector/qgsvectorlayersavestyledialog.h @@ -1,5 +1,5 @@ /*************************************************************************** - qgsmaplayersavestyledialog.h + qgsvectorlayersavestyledialog.h -------------------------------------- Date : September 2018 Copyright : (C) 2018 by Denis Rouzaud @@ -13,31 +13,29 @@ * * ***************************************************************************/ -#ifndef QGSMAPLAYERSAVESTYLEDIALOG_H -#define QGSMAPLAYERSAVESTYLEDIALOG_H +#ifndef QGSVECTORLAYERSAVESTYLEDIALOG_H +#define QGSVECTORLAYERSAVESTYLEDIALOG_H // We don't want to expose this in the public API #define SIP_NO_FILE #include -#include "ui_qgsmaplayersavestyledialog.h" -#include "qgsmaplayer.h" -#include "qgslayerpropertiesdialog.h" +#include "ui_qgsvectorlayersavestyledialog.h" +#include "qgsvectorlayerproperties.h" #include "qgis_gui.h" +class QgsVectorLayer; class QgsMapLayerStyleCategoriesModel; /** * \ingroup gui - * \class QgsMapLayerSaveStyleDialog + * \class QgsVectorLayerSaveStyleDialog * - * \brief The QgsMapLayerSaveStyleDialog class provides the UI to save the current style + * \brief The QgsVectorLayerSaveStyleDialog class provides the UI to save the current style * or multiple styles into different storage containers (QML, SLD and DB). * The user can select what categories must be saved. - * - * \since QGIS 3.34 */ -class GUI_EXPORT QgsMapLayerSaveStyleDialog : public QDialog, private Ui::QgsMapLayerSaveStyleDialog +class GUI_EXPORT QgsVectorLayerSaveStyleDialog : public QDialog, private Ui::QgsVectorLayerSaveStyleDialog { Q_OBJECT @@ -52,48 +50,17 @@ class GUI_EXPORT QgsMapLayerSaveStyleDialog : public QDialog, private Ui::QgsMap bool isDefault; }; - /** - * Constructor - */ - explicit QgsMapLayerSaveStyleDialog( QgsMapLayer *layer, QWidget *parent = nullptr ); + explicit QgsVectorLayerSaveStyleDialog( QgsVectorLayer *layer, QWidget *parent = nullptr ); - /** - * Returns the database settings for saving the style in the DB. - */ SaveToDbSettings saveToDbSettings() const; - - /** - * Returns the selected file output path. - */ QString outputFilePath() const; - - /** - * Returns the available style categories. - */ QgsMapLayer::StyleCategories styleCategories() const; - /** - * Returns the selected style storage type. - */ - QgsLayerPropertiesDialog::StyleType currentStyleType() const; + QgsVectorLayerProperties::StyleType currentStyleType() const; - /** - * Returns whether the user only allowed to save the current style. - * - * \see setSaveOnlyCurrentStyle() - */ bool saveOnlyCurrentStyle() const; - - /** - * Sets whether the user only allowed to save the current style. - * - * \see saveOnlyCurrentStyle() - */ void setSaveOnlyCurrentStyle( bool saveCurrentStyle ); - /** - * Returns the styles list widget. - */ const QListWidget *stylesWidget( ); /** @@ -113,10 +80,10 @@ class GUI_EXPORT QgsMapLayerSaveStyleDialog : public QDialog, private Ui::QgsMap private: void setupMultipleStyles(); void populateStyleComboBox(); - QgsMapLayer *mLayer = nullptr; + QgsVectorLayer *mLayer = nullptr; QgsMapLayerStyleCategoriesModel *mModel; QString mUiFileContent; bool mSaveOnlyCurrentStyle = true; }; -#endif // QGSMAPLAYERSAVESTYLEDIALOG_H +#endif // QGSVECTORLAYERSAVESTYLE_H diff --git a/src/providers/mssql/qgsmssqlprovider.cpp b/src/providers/mssql/qgsmssqlprovider.cpp index 836243e0752..3f547af65f7 100644 --- a/src/providers/mssql/qgsmssqlprovider.cpp +++ b/src/providers/mssql/qgsmssqlprovider.cpp @@ -1129,17 +1129,6 @@ bool QgsMssqlProvider::isValid() const return mValid; } -Qgis::ProviderStyleStorageCapabilities QgsMssqlProvider::styleStorageCapabilities() const -{ - Qgis::ProviderStyleStorageCapabilities storageCapabilities; - if ( isValid() ) - { - storageCapabilities |= Qgis::ProviderStyleStorageCapability::SaveToDatabase; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::LoadFromDatabase; - } - return storageCapabilities; -} - bool QgsMssqlProvider::addFeatures( QgsFeatureList &flist, Flags flags ) { for ( QgsFeatureList::iterator it = flist.begin(); it != flist.end(); ++it ) diff --git a/src/providers/mssql/qgsmssqlprovider.h b/src/providers/mssql/qgsmssqlprovider.h index ca867f1aa3b..24c784777a0 100644 --- a/src/providers/mssql/qgsmssqlprovider.h +++ b/src/providers/mssql/qgsmssqlprovider.h @@ -115,7 +115,7 @@ class QgsMssqlProvider final: public QgsVectorDataProvider bool isValid() const override; - Qgis::ProviderStyleStorageCapabilities styleStorageCapabilities() const override; + bool isSaveAndLoadStyleToDatabaseSupported() const override { return true; } bool addFeatures( QgsFeatureList &flist, QgsFeatureSink::Flags flags = QgsFeatureSink::Flags() ) override; diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp index 67264c2f5ee..91407b79916 100644 --- a/src/providers/oracle/qgsoracleprovider.cpp +++ b/src/providers/oracle/qgsoracleprovider.cpp @@ -337,14 +337,6 @@ bool QgsOracleProvider::execLoggedStatic( QSqlQuery &qry, const QString &sql, co return res; } -Qgis::ProviderStyleStorageCapabilities QgsOracleProvider::styleStorageCapabilities() const -{ - Qgis::ProviderStyleStorageCapabilities storageCapabilities; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::SaveToDatabase; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::LoadFromDatabase; - return storageCapabilities; -} - void QgsOracleProvider::setTransaction( QgsTransaction *transaction ) { // static_cast since layers cannot be added to a transaction of a non-matching provider diff --git a/src/providers/oracle/qgsoracleprovider.h b/src/providers/oracle/qgsoracleprovider.h index 2778f52bfd2..bbba645c881 100644 --- a/src/providers/oracle/qgsoracleprovider.h +++ b/src/providers/oracle/qgsoracleprovider.h @@ -185,7 +185,7 @@ class QgsOracleProvider final: public QgsVectorDataProvider static bool execLoggedStatic( QSqlQuery &qry, const QString &sql, const QVariantList &args, const QString &uri, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ); - Qgis::ProviderStyleStorageCapabilities styleStorageCapabilities() const override; + bool isSaveAndLoadStyleToDatabaseSupported() const override { return true; } void setTransaction( QgsTransaction *transaction ) override; QgsTransaction *transaction() const override; diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index 15c36ead1f6..030b9a114f5 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -2269,18 +2269,6 @@ bool QgsPostgresProvider::isValid() const return mValid; } -Qgis::ProviderStyleStorageCapabilities QgsPostgresProvider::styleStorageCapabilities() const -{ - Qgis::ProviderStyleStorageCapabilities storageCapabilities; - if ( isValid() ) - { - storageCapabilities |= Qgis::ProviderStyleStorageCapability::SaveToDatabase; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::LoadFromDatabase; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::DeleteFromDatabase; - } - return storageCapabilities; -} - QString QgsPostgresProvider::defaultValueClause( int fieldId ) const { QString defVal = mDefaultValues.value( fieldId, QString() ); diff --git a/src/providers/postgres/qgspostgresprovider.h b/src/providers/postgres/qgspostgresprovider.h index 10749a09d76..abdb48dd185 100644 --- a/src/providers/postgres/qgspostgresprovider.h +++ b/src/providers/postgres/qgspostgresprovider.h @@ -154,7 +154,8 @@ class QgsPostgresProvider final: public QgsVectorDataProvider QgsFeedback *feedback = nullptr ) const override; void enumValues( int index, QStringList &enumList ) const override; bool isValid() const override; - Qgis::ProviderStyleStorageCapabilities styleStorageCapabilities() const override; + bool isSaveAndLoadStyleToDatabaseSupported() const override { return true; } + bool isDeleteStyleFromDatabaseSupported() const override { return true; } QgsAttributeList attributeIndexes() const override; QgsAttributeList pkAttributeIndexes() const override { return mPrimaryKeyAttrs; } QString defaultValueClause( int fieldId ) const override; diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index 43ab6fe4398..126a145523c 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -3781,15 +3781,9 @@ bool QgsSpatiaLiteProvider::isValid() const return mValid; } -Qgis::ProviderStyleStorageCapabilities QgsSpatiaLiteProvider::styleStorageCapabilities() const +bool QgsSpatiaLiteProvider::isSaveAndLoadStyleToDatabaseSupported() const { - Qgis::ProviderStyleStorageCapabilities storageCapabilities; - if ( isValid() ) - { - storageCapabilities |= Qgis::ProviderStyleStorageCapability::SaveToDatabase; - storageCapabilities |= Qgis::ProviderStyleStorageCapability::LoadFromDatabase; - } - return storageCapabilities; + return mValid; } QString QgsSpatiaLiteProvider::name() const diff --git a/src/providers/spatialite/qgsspatialiteprovider.h b/src/providers/spatialite/qgsspatialiteprovider.h index 1d1d1f39198..eb2d1379f50 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.h +++ b/src/providers/spatialite/qgsspatialiteprovider.h @@ -114,7 +114,7 @@ class QgsSpatiaLiteProvider final: public QgsVectorDataProvider QgsFeedback *feedback = nullptr ) const override; bool isValid() const override; - Qgis::ProviderStyleStorageCapabilities styleStorageCapabilities() const override; + bool isSaveAndLoadStyleToDatabaseSupported() const override; bool addFeatures( QgsFeatureList &flist, QgsFeatureSink::Flags flags = QgsFeatureSink::Flags() ) override; bool deleteFeatures( const QgsFeatureIds &id ) override; bool truncate() override; diff --git a/src/ui/qgsmaplayersavestyledialog.ui b/src/ui/qgsvectorlayersavestyledialog.ui similarity index 96% rename from src/ui/qgsmaplayersavestyledialog.ui rename to src/ui/qgsvectorlayersavestyledialog.ui index 669746fa0ee..12db1387c69 100644 --- a/src/ui/qgsmaplayersavestyledialog.ui +++ b/src/ui/qgsvectorlayersavestyledialog.ui @@ -1,7 +1,7 @@ - QgsMapLayerSaveStyleDialog - + QgsVectorLayerSaveStyleDialog + 0 @@ -200,7 +200,7 @@ buttonBox accepted() - QgsMapLayerSaveStyleDialog + QgsVectorLayerSaveStyleDialog accept() @@ -216,7 +216,7 @@ buttonBox rejected() - QgsMapLayerSaveStyleDialog + QgsVectorLayerSaveStyleDialog reject() diff --git a/tests/src/python/test_provider_mssql.py b/tests/src/python/test_provider_mssql.py index b5774364bca..6d93c939cc7 100644 --- a/tests/src/python/test_provider_mssql.py +++ b/tests/src/python/test_provider_mssql.py @@ -15,7 +15,6 @@ import qgis # NOQA from qgis.PyQt.QtCore import QDate, QDateTime, QDir, QTime, QVariant from qgis.core import ( NULL, - Qgis, QgsCoordinateReferenceSystem, QgsDataProvider, QgsDataSourceUri, @@ -404,8 +403,8 @@ class TestPyQgsMssqlProvider(QgisTestCase, ProviderTestCase): vl = self.getSource() self.assertTrue(vl.isValid()) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.LoadFromDatabase, Qgis.ProviderStyleStorageCapability.LoadFromDatabase) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.SaveToDatabase, Qgis.ProviderStyleStorageCapability.SaveToDatabase) + self.assertTrue( + vl.dataProvider().isSaveAndLoadStyleToDatabaseSupported()) # table layer_styles does not exist diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py index b59d61c998b..f3ae9da33db 100644 --- a/tests/src/python/test_provider_ogr_gpkg.py +++ b/tests/src/python/test_provider_ogr_gpkg.py @@ -667,8 +667,7 @@ class TestPyQgsOGRProviderGpkg(QgisTestCase): # First test with invalid URI vl = QgsVectorLayer('/idont/exist.gpkg', 'test', 'ogr') - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.LoadFromDatabase, 0) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.SaveToDatabase, 0) + self.assertFalse(vl.dataProvider().isSaveAndLoadStyleToDatabaseSupported()) res, err = QgsProviderRegistry.instance().styleExists('ogr', '/idont/exist.gpkg', '') self.assertFalse(res) @@ -717,8 +716,7 @@ class TestPyQgsOGRProviderGpkg(QgisTestCase): vl2 = QgsVectorLayer(f'{tmpfile}|layername=test2', 'test2', 'ogr') self.assertTrue(vl2.isValid()) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.LoadFromDatabase, Qgis.ProviderStyleStorageCapability.LoadFromDatabase) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.SaveToDatabase, Qgis.ProviderStyleStorageCapability.SaveToDatabase) + self.assertTrue(vl.dataProvider().isSaveAndLoadStyleToDatabaseSupported()) # style tables don't exist yet res, err = QgsProviderRegistry.instance().styleExists('ogr', vl.source(), '') @@ -1157,8 +1155,7 @@ class TestPyQgsOGRProviderGpkg(QgisTestCase): if count > 0: # We should have just 1 but for obscure reasons # uniqueFields() (sometimes?) leaves one behind - # Even more obscure reasons a third FD remains open - self.assertIn(count, (1, 2, 3)) + self.assertIn(count, (1, 2)) for i in range(70): got = [feat for feat in vl.getFeatures()] @@ -1169,7 +1166,7 @@ class TestPyQgsOGRProviderGpkg(QgisTestCase): # one shared by the feature iterators count = count_opened_filedescriptors(tmpfile) if count > 0: - self.assertIn(count, (2, 3)) + self.assertEqual(count, 2) # Re-open an already opened layers. We should get a new handle layername = 'layer%d' % 0 diff --git a/tests/src/python/test_provider_postgres.py b/tests/src/python/test_provider_postgres.py index c25c426a685..fff74b0dab2 100644 --- a/tests/src/python/test_provider_postgres.py +++ b/tests/src/python/test_provider_postgres.py @@ -1941,9 +1941,9 @@ class TestPyQgsPostgresProvider(QgisTestCase, ProviderTestCase): vl = self.getEditableLayer() self.assertTrue(vl.isValid()) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.LoadFromDatabase, Qgis.ProviderStyleStorageCapability.LoadFromDatabase) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.SaveToDatabase, Qgis.ProviderStyleStorageCapability.SaveToDatabase) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.DeleteFromDatabase, Qgis.ProviderStyleStorageCapability.DeleteFromDatabase) + self.assertTrue( + vl.dataProvider().isSaveAndLoadStyleToDatabaseSupported()) + self.assertTrue(vl.dataProvider().isDeleteStyleFromDatabaseSupported()) # table layer_styles does not exist @@ -2125,9 +2125,9 @@ class TestPyQgsPostgresProvider(QgisTestCase, ProviderTestCase): vl = self.getEditableLayer() self.assertTrue(vl.isValid()) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.LoadFromDatabase, Qgis.ProviderStyleStorageCapability.LoadFromDatabase) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.SaveToDatabase, Qgis.ProviderStyleStorageCapability.SaveToDatabase) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.DeleteFromDatabase, Qgis.ProviderStyleStorageCapability.DeleteFromDatabase) + self.assertTrue( + vl.dataProvider().isSaveAndLoadStyleToDatabaseSupported()) + self.assertTrue(vl.dataProvider().isDeleteStyleFromDatabaseSupported()) mFilePath = QDir.toNativeSeparators( f"{unitTestDataPath()}/symbol_layer/fontSymbol.qml") diff --git a/tests/src/python/test_provider_spatialite.py b/tests/src/python/test_provider_spatialite.py index 156d3e357cd..3d07ef3d8cb 100644 --- a/tests/src/python/test_provider_spatialite.py +++ b/tests/src/python/test_provider_spatialite.py @@ -1096,8 +1096,7 @@ class TestQgsSpatialiteProvider(QgisTestCase, ProviderTestCase): # First test with invalid URI vl = QgsVectorLayer('/idont/exist.sqlite', 'test', 'spatialite') - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.LoadFromDatabase, 0) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.SaveToDatabase, 0) + self.assertFalse(vl.dataProvider().isSaveAndLoadStyleToDatabaseSupported()) res, err = QgsProviderRegistry.instance().styleExists('spatialite', '/idont/exist.sqlite', '') self.assertFalse(res) @@ -1151,8 +1150,7 @@ class TestQgsSpatialiteProvider(QgisTestCase, ProviderTestCase): vl = QgsVectorLayer(testPath, 'test', 'spatialite') self.assertTrue(vl.isValid()) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.LoadFromDatabase, Qgis.ProviderStyleStorageCapability.LoadFromDatabase) - self.assertEqual(int(vl.dataProvider().styleStorageCapabilities()) & Qgis.ProviderStyleStorageCapability.SaveToDatabase, Qgis.ProviderStyleStorageCapability.SaveToDatabase) + self.assertTrue(vl.dataProvider().isSaveAndLoadStyleToDatabaseSupported()) # style tables don't exist yet res, err = QgsProviderRegistry.instance().styleExists('spatialite', vl.source(), '')