allow to save/read enum/flags as map layer properties (#44329)

This commit is contained in:
Denis Rouzaud 2021-07-23 10:41:45 +02:00 committed by GitHub
parent b0319105bc
commit eaa4b54e20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 219 additions and 0 deletions

View File

@ -696,6 +696,8 @@ Read all custom properties from layer. Properties are stored in a map and saved
.. versionadded:: 3.14
%End
void removeCustomProperty( const QString &key );
%Docstring
Remove a custom property from layer. Properties are stored in a map and saved in project file.

View File

@ -40,6 +40,7 @@
#include "qgsreadwritecontext.h"
#include "qgsdataprovider.h"
#include "qgis.h"
#include "qgslogger.h"
class QgsAbstract3DRenderer;
class QgsDataProvider;
@ -675,6 +676,165 @@ class CORE_EXPORT QgsMapLayer : public QObject
*/
const QgsObjectCustomProperties &customProperties() const;
#ifndef SIP_RUN
/**
* Returns the property value for a property based on an enum.
* This forces the output to be a valid and existing entry of the enum.
* Hence if the property value is incorrect, the given default value is returned.
* This tries first with property as a string (as the enum) and then as an integer value.
* \note The enum needs to be declared with Q_ENUM, and flags with Q_FLAG (not Q_FLAGS).
* \see setCustomEnumProperty
* \see customFlagProperty
* \since QGIS 3.22
*/
template <class T>
T customEnumProperty( const QString &key, const T &defaultValue )
{
QMetaEnum metaEnum = QMetaEnum::fromType<T>();
Q_ASSERT( metaEnum.isValid() );
if ( !metaEnum.isValid() )
{
QgsDebugMsg( QStringLiteral( "Invalid metaenum. Enum probably misses Q_ENUM or Q_FLAG declaration." ) );
}
T v;
bool ok = false;
if ( metaEnum.isValid() )
{
// read as string
QByteArray ba = customProperty( key, metaEnum.valueToKey( static_cast<int>( defaultValue ) ) ).toString().toUtf8();
const char *vs = ba.data();
v = static_cast<T>( metaEnum.keyToValue( vs, &ok ) );
if ( ok )
return v;
}
// if failed, try to read as int (old behavior)
// this code shall be removed later
// then the method could be marked as const
v = static_cast<T>( customProperty( key, static_cast<int>( defaultValue ) ).toInt( &ok ) );
if ( metaEnum.isValid() )
{
if ( !ok || !metaEnum.valueToKey( static_cast<int>( v ) ) )
{
v = defaultValue;
}
else
{
// found property as an integer
// convert the property to the new form (string)
setCustomEnumProperty( key, v );
}
}
return v;
}
/**
* Set the value of a property based on an enum.
* The property will be saved as string.
* \note The enum needs to be declared with Q_ENUM, and flags with Q_FLAG (not Q_FLAGS).
* \see customEnumProperty
* \see setCustomFlagProperty
* \since QGIS 3.22
*/
template <class T>
void setCustomEnumProperty( const QString &key, const T &value )
{
QMetaEnum metaEnum = QMetaEnum::fromType<T>();
Q_ASSERT( metaEnum.isValid() );
if ( metaEnum.isValid() )
{
setCustomProperty( key, metaEnum.valueToKey( static_cast<int>( value ) ) );
}
else
{
QgsDebugMsg( QStringLiteral( "Invalid metaenum. Enum probably misses Q_ENUM or Q_FLAG declaration." ) );
}
}
/**
* Returns the property value for a property based on a flag.
* This forces the output to be a valid and existing entry of the flag.
* Hence if the property value is incorrect, the given default value is returned.
* This tries first with property as a string (using a byte array) and then as an integer value.
* \note The flag needs to be declared with Q_FLAG (not Q_FLAGS).
* \note for Python bindings, a custom implementation is achieved in Python directly.
* \see setCustomFlagProperty
* \see customEnumProperty
* \since QGIS 3.22
*/
template <class T>
T customFlagProperty( const QString &key, const T &defaultValue )
{
QMetaEnum metaEnum = QMetaEnum::fromType<T>();
Q_ASSERT( metaEnum.isValid() );
if ( !metaEnum.isValid() )
{
QgsDebugMsg( QStringLiteral( "Invalid metaenum. Enum probably misses Q_ENUM or Q_FLAG declaration." ) );
}
T v = defaultValue;
bool ok = false;
if ( metaEnum.isValid() )
{
// read as string
QByteArray ba = customProperty( key, metaEnum.valueToKeys( defaultValue ) ).toString().toUtf8();
const char *vs = ba.data();
v = static_cast<T>( metaEnum.keysToValue( vs, &ok ) );
}
if ( !ok )
{
// if failed, try to read as int (old behavior)
// this code shall be removed later
// then the method could be marked as const
v = T( customProperty( key, static_cast<int>( defaultValue ) ).toInt( &ok ) );
if ( metaEnum.isValid() )
{
if ( !ok || metaEnum.valueToKeys( static_cast<int>( v ) ).isEmpty() )
{
v = defaultValue;
}
else
{
// found property as an integer
// convert the property to the new form (string)
setCustomFlagProperty( key, v );
}
}
}
return v;
}
/**
* Set the value of a property based on a flag.
* The property will be saved as string.
* \note The flag needs to be declared with Q_FLAG (not Q_FLAGS).
* \see customFlagProperty
* \see customEnumProperty
* \since QGIS 3.22
*/
template <class T>
void setCustomFlagProperty( const QString &key, const T &value )
{
QMetaEnum metaEnum = QMetaEnum::fromType<T>();
Q_ASSERT( metaEnum.isValid() );
if ( metaEnum.isValid() )
{
setCustomProperty( key, metaEnum.valueToKeys( value ) );
}
else
{
QgsDebugMsg( QStringLiteral( "Invalid metaenum. Enum probably misses Q_ENUM or Q_FLAG declaration." ) );
}
}
#endif
/**
* Remove a custom property from layer. Properties are stored in a map and saved in project file.
* \see setCustomProperty()

View File

@ -28,6 +28,7 @@
#include <qgsproviderregistry.h>
#include "qgsvectorlayerref.h"
#include "qgsmaplayerlistutils.h"
#include "qgsmaplayerproxymodel.h"
/**
* \ingroup UnitTests
@ -64,6 +65,8 @@ class TestQgsMapLayer : public QObject
void notify();
void customEnumFlagProperties();
private:
QgsVectorLayer *mpLayer = nullptr;
};
@ -387,5 +390,59 @@ void TestQgsMapLayer::notify()
QCOMPARE( spyDataChanged.count(), 2 );
}
void TestQgsMapLayer::customEnumFlagProperties()
{
std::unique_ptr<QgsVectorLayer> ml = std::make_unique<QgsVectorLayer>( QStringLiteral( "Point" ), QStringLiteral( "name" ), QStringLiteral( "memory" ) );
// assign to inexisting property
ml->setCustomProperty( QStringLiteral( "my_property_for_units" ), -1 );
ml->setCustomProperty( QStringLiteral( "my_property_for_units_as_string" ), QStringLiteral( "myString" ) );
// just to be sure it really doesn't exist
QVERIFY( static_cast<int>( QgsUnitTypes::LayoutMeters ) != -1 );
// standard method returns invalid property
int v1 = ml->customProperty( QStringLiteral( "my_property_for_units" ), QgsUnitTypes::LayoutMeters ).toInt();
QCOMPARE( v1, -1 );
// enum method returns default property if current property is incorrect
QgsUnitTypes::LayoutUnit v2 = ml->customEnumProperty( QStringLiteral( "my_property_for_units" ), QgsUnitTypes::LayoutMeters );
QCOMPARE( v2, QgsUnitTypes::LayoutMeters );
QgsUnitTypes::LayoutUnit v2s = ml->customEnumProperty( QStringLiteral( "my_property_for_units_as_string" ), QgsUnitTypes::LayoutMeters );
QCOMPARE( v2s, QgsUnitTypes::LayoutMeters );
// test a different property than default
ml->setCustomEnumProperty( QStringLiteral( "my_property_for_units" ), QgsUnitTypes::LayoutCentimeters );
QgsUnitTypes::LayoutUnit v3 = ml->customEnumProperty( QStringLiteral( "my_property_for_units" ), QgsUnitTypes::LayoutMeters );
QCOMPARE( v3, QgsUnitTypes::LayoutCentimeters );
ml->setCustomEnumProperty( QStringLiteral( "my_property_for_units" ), QgsUnitTypes::LayoutCentimeters );
// auto conversion of old ml (int to str)
QCOMPARE( ml->customProperty( "my_property_for_units" ).toString(), QStringLiteral( "LayoutCentimeters" ) );
QgsUnitTypes::LayoutUnit v3s = ml->customEnumProperty( QStringLiteral( "my_property_for_units" ), QgsUnitTypes::LayoutMeters );
QCOMPARE( v3s, QgsUnitTypes::LayoutCentimeters );
QString v3ss = ml->customProperty( QStringLiteral( "my_property_for_units" ), QStringLiteral( "myDummyValue" ) ).toString();
QCOMPARE( v3ss, QStringLiteral( "LayoutCentimeters" ) );
// Flags
QgsMapLayerProxyModel::Filters pointAndLine = QgsMapLayerProxyModel::Filters( QgsMapLayerProxyModel::PointLayer | QgsMapLayerProxyModel::LineLayer );
QgsMapLayerProxyModel::Filters pointAndPolygon = QgsMapLayerProxyModel::Filters( QgsMapLayerProxyModel::PointLayer | QgsMapLayerProxyModel::PolygonLayer );
ml->setCustomProperty( QStringLiteral( "my_property_for_a_flag" ), 1e8 ); // invalid
// this should be switched to customFlagProperty (see https://stackoverflow.com/questions/68485843/how-to-test-if-a-value-is-valid-for-a-qflags)
QgsMapLayerProxyModel::Filters v4 = ml->customEnumProperty( QStringLiteral( "my_property_for_a_flag" ), pointAndLine );
QCOMPARE( v4, pointAndLine );
ml->setCustomProperty( QStringLiteral( "my_property_for_a_flag" ), static_cast<int>( pointAndPolygon ) );
QgsMapLayerProxyModel::Filters v5 = ml->customFlagProperty( QStringLiteral( "my_property_for_a_flag" ), pointAndLine );
QCOMPARE( v5, pointAndPolygon );
// auto conversion of old property (int to str)
QCOMPARE( ml->customProperty( "my_property_for_a_flag" ).toString(), QStringLiteral( "PointLayer|PolygonLayer" ) );
ml->setCustomFlagProperty( QStringLiteral( "my_property_for_a_flag_as_string" ), pointAndPolygon );
QgsMapLayerProxyModel::Filters v5s = ml->customFlagProperty( QStringLiteral( "my_property_for_a_flag_as_string" ), pointAndLine );
QCOMPARE( v5s, pointAndPolygon );
QString v5ss = ml->customProperty( QStringLiteral( "my_property_for_a_flag_as_string" ), QStringLiteral( "myDummyString" ) ).toString();
QCOMPARE( v5ss, QStringLiteral( "PointLayer|PolygonLayer" ) );
}
QGSTEST_MAIN( TestQgsMapLayer )
#include "testqgsmaplayer.moc"