Add python safe addSmartgroup method, unit tests for smart groups

This commit is contained in:
Nyall Dawson 2018-09-12 07:17:28 +10:00
parent c41af120a0
commit 95d65ae861
4 changed files with 141 additions and 16 deletions

View File

@ -86,6 +86,21 @@ Adds a new tag and returns the tag's id
%End
int addSmartgroup( const QString &name, const QString &op, const QStringList &matchTag, const QStringList &noMatchTag,
const QStringList &matchName, const QStringList &noMatchName );
%Docstring
Adds a new smartgroup to the database and returns the id.
:param name: is the name of the new Smart Group to be added
:param op: is the operator between the conditions; AND/OR as QString
:param matchTag: list of strings to match within tags
:param noMatchTag: list of strings to exclude matches from tags
:param matchName: list of string to match within names
:param noMatchName: list of strings to exclude matches from names
.. versionadded:: 3.4
%End
QStringList tags() const;
%Docstring
Returns a list of all tags in the style database

View File

@ -1169,26 +1169,34 @@ int QgsStyle::smartgroupId( const QString &name )
}
int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QgsSmartConditionMap &conditions )
{
return addSmartgroup( name, op, conditions.values( QStringLiteral( "tag" ) ),
conditions.values( QStringLiteral( "!tag" ) ),
conditions.values( QStringLiteral( "name" ) ),
conditions.values( QStringLiteral( "!name" ) ) );
}
int QgsStyle::addSmartgroup( const QString &name, const QString &op, const QStringList &matchTag, const QStringList &noMatchTag, const QStringList &matchName, const QStringList &noMatchName )
{
QDomDocument doc( QStringLiteral( "dummy" ) );
QDomElement smartEl = doc.createElement( QStringLiteral( "smartgroup" ) );
smartEl.setAttribute( QStringLiteral( "name" ), name );
smartEl.setAttribute( QStringLiteral( "operator" ), op );
QStringList constraints;
constraints << QStringLiteral( "tag" ) << QStringLiteral( "group" ) << QStringLiteral( "name" ) << QStringLiteral( "!tag" ) << QStringLiteral( "!group" ) << QStringLiteral( "!name" );
Q_FOREACH ( const QString &constraint, constraints )
auto addCondition = [&doc, &smartEl]( const QString & constraint, const QStringList & parameters )
{
QStringList parameters = conditions.values( constraint );
Q_FOREACH ( const QString &param, parameters )
for ( const QString &param : parameters )
{
QDomElement condEl = doc.createElement( QStringLiteral( "condition" ) );
condEl.setAttribute( QStringLiteral( "constraint" ), constraint );
condEl.setAttribute( QStringLiteral( "param" ), param );
smartEl.appendChild( condEl );
}
}
};
addCondition( QStringLiteral( "tag" ), matchTag );
addCondition( QStringLiteral( "!tag" ), noMatchTag );
addCondition( QStringLiteral( "name" ), matchName );
addCondition( QStringLiteral( "!name" ), noMatchName );
QByteArray xmlArray;
QTextStream stream( &xmlArray );
@ -1312,16 +1320,16 @@ QStringList QgsStyle::symbolsOfSmartgroup( StyleEntity type, int id )
else if ( constraint == QLatin1String( "!tag" ) )
{
resultNames = type == SymbolEntity ? symbolNames() : colorRampNames();
QStringList unwanted = symbolsWithTag( type, tagId( param ) );
Q_FOREACH ( const QString &name, unwanted )
const QStringList unwanted = symbolsWithTag( type, tagId( param ) );
for ( const QString &name : unwanted )
{
resultNames.removeAll( name );
}
}
else if ( constraint == QLatin1String( "!name" ) )
{
QStringList all = type == SymbolEntity ? symbolNames() : colorRampNames();
Q_FOREACH ( const QString &str, all )
const QStringList all = type == SymbolEntity ? symbolNames() : colorRampNames();
for ( const QString &str : all )
{
if ( !str.contains( param, Qt::CaseInsensitive ) )
resultNames << str;
@ -1344,7 +1352,7 @@ QStringList QgsStyle::symbolsOfSmartgroup( StyleEntity type, int id )
{
QStringList dummy = symbols;
symbols.clear();
Q_FOREACH ( const QString &result, resultNames )
for ( const QString &result : qgis::as_const( resultNames ) )
{
if ( dummy.contains( result ) )
symbols << result;
@ -1354,7 +1362,10 @@ QStringList QgsStyle::symbolsOfSmartgroup( StyleEntity type, int id )
} // DOM loop ends here
}
return symbols;
// return sorted, unique list
QStringList unique = symbols.toSet().toList();
std::sort( unique.begin(), unique.end() );
return unique;
}
QgsSmartConditionMap QgsStyle::smartgroup( int id )
@ -1437,7 +1448,7 @@ bool QgsStyle::exportXml( const QString &filename )
QDomDocument doc( QStringLiteral( "qgis_style" ) );
QDomElement root = doc.createElement( QStringLiteral( "qgis_style" ) );
root.setAttribute( QStringLiteral( "version" ), STYLE_CURRENT_VERSION );
root.setAttribute( QStringLiteral( "version" ), QStringLiteral( STYLE_CURRENT_VERSION ) );
doc.appendChild( root );
QStringList favoriteSymbols = symbolsOfFavorite( SymbolEntity );
@ -1528,7 +1539,7 @@ bool QgsStyle::importXml( const QString &filename )
}
QString version = docEl.attribute( QStringLiteral( "version" ) );
if ( version != STYLE_CURRENT_VERSION && version != QLatin1String( "0" ) )
if ( version != QLatin1String( STYLE_CURRENT_VERSION ) && version != QLatin1String( "0" ) )
{
mErrorString = "Unknown style file version: " + version;
return false;
@ -1543,7 +1554,7 @@ bool QgsStyle::importXml( const QString &filename )
auto query = QgsSqlite3Mprintf( "BEGIN TRANSACTION;" );
runEmptyQuery( query );
if ( version == STYLE_CURRENT_VERSION )
if ( version == QLatin1String( STYLE_CURRENT_VERSION ) )
{
// For the new style, load symbols individually
while ( !e.isNull() )

View File

@ -140,6 +140,21 @@ class CORE_EXPORT QgsStyle : public QObject
*/
int addSmartgroup( const QString &name, const QString &op, const QgsSmartConditionMap &conditions ) SIP_SKIP;
/**
* Adds a new smartgroup to the database and returns the id.
*
* \param name is the name of the new Smart Group to be added
* \param op is the operator between the conditions; AND/OR as QString
* \param matchTag list of strings to match within tags
* \param noMatchTag list of strings to exclude matches from tags
* \param matchName list of string to match within names
* \param noMatchName list of strings to exclude matches from names
*
* \since QGIS 3.4
*/
int addSmartgroup( const QString &name, const QString &op, const QStringList &matchTag, const QStringList &noMatchTag,
const QStringList &matchName, const QStringList &noMatchName );
/**
* Returns a list of all tags in the style database
*

View File

@ -67,6 +67,7 @@ class TestStyle : public QObject
void testSaveLoad();
void testFavorites();
void testTags();
void testSmartGroup();
};
@ -551,5 +552,88 @@ void TestStyle::testTags()
QCOMPARE( tagsChangedSpy.at( 13 ).at( 2 ).toStringList(), QStringList() );
}
void TestStyle::testSmartGroup()
{
QgsStyle style;
style.createMemoryDatabase();
QSignalSpy groupModifiedSpy( &style, &QgsStyle::groupsModified );
std::unique_ptr< QgsMarkerSymbol > sym1( QgsMarkerSymbol::createSimple( QgsStringMap() ) );
std::unique_ptr< QgsMarkerSymbol > sym2( QgsMarkerSymbol::createSimple( QgsStringMap() ) );
std::unique_ptr< QgsMarkerSymbol > sym3( QgsMarkerSymbol::createSimple( QgsStringMap() ) );
style.addSymbol( QStringLiteral( "symbolA" ), sym1->clone(), true );
style.addSymbol( QStringLiteral( "symbolB" ), sym2->clone(), true );
style.addSymbol( QStringLiteral( "symbolC" ), sym3->clone(), true );
QgsLimitedRandomColorRamp *randomRamp = new QgsLimitedRandomColorRamp();
QVERIFY( style.addColorRamp( "ramp a", randomRamp, true ) );
randomRamp = new QgsLimitedRandomColorRamp();
QVERIFY( style.addColorRamp( "different bbb", randomRamp, true ) );
QVERIFY( style.smartgroupNames().empty() );
QVERIFY( style.smartgroup( 5 ).isEmpty() );
QCOMPARE( style.smartgroupId( QStringLiteral( "no exist" ) ), 0 );
int res = style.addSmartgroup( QStringLiteral( "mine" ), QStringLiteral( "AND" ), QStringList(), QStringList(), QStringList() << QStringLiteral( "a" ), QStringList() );
QCOMPARE( res, 1 );
QCOMPARE( style.smartgroupNames(), QStringList() << QStringLiteral( "mine" ) );
QCOMPARE( style.smartgroup( 1 ).values( QStringLiteral( "name" ) ), QStringList() << QStringLiteral( "a" ) );
QCOMPARE( style.smartgroupId( QStringLiteral( "mine" ) ), 1 );
QCOMPARE( groupModifiedSpy.count(), 1 );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::SymbolEntity, 1 ), QStringList() << QStringLiteral( "symbolA" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::ColorrampEntity, 1 ), QStringList() << QStringLiteral( "ramp a" ) );
res = style.addSmartgroup( QStringLiteral( "tag" ), QStringLiteral( "OR" ), QStringList(), QStringList(), QStringList() << "c", QStringList() << "a" );
QCOMPARE( res, 2 );
QCOMPARE( style.smartgroupNames(), QStringList() << QStringLiteral( "mine" ) << QStringLiteral( "tag" ) );
QCOMPARE( style.smartgroup( 2 ).values( QStringLiteral( "name" ) ), QStringList() << QStringLiteral( "c" ) );
QCOMPARE( style.smartgroup( 2 ).values( QStringLiteral( "!name" ) ), QStringList() << QStringLiteral( "a" ) );
QCOMPARE( style.smartgroupId( QStringLiteral( "tag" ) ), 2 );
QCOMPARE( groupModifiedSpy.count(), 2 );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::SymbolEntity, 2 ), QStringList() << QStringLiteral( "symbolB" ) << QStringLiteral( "symbolC" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::ColorrampEntity, 2 ), QStringList() << QStringLiteral( "different bbb" ) );
// tag some symbols
style.tagSymbol( QgsStyle::SymbolEntity, "symbolA", QStringList() << "red" << "blue" );
style.tagSymbol( QgsStyle::SymbolEntity, "symbolB", QStringList() << "blue" );
style.tagSymbol( QgsStyle::ColorrampEntity, "ramp a", QStringList() << "blue" );
style.tagSymbol( QgsStyle::ColorrampEntity, "different bbb", QStringList() << "blue" << "red" );
// adding tags modifies groups!
QCOMPARE( groupModifiedSpy.count(), 4 );
res = style.addSmartgroup( QStringLiteral( "tags" ), QStringLiteral( "AND" ), QStringList() << "blue", QStringList() << "red", QStringList(), QStringList() );
QCOMPARE( res, 3 );
QCOMPARE( style.smartgroupNames(), QStringList() << QStringLiteral( "mine" ) << QStringLiteral( "tag" ) << QStringLiteral( "tags" ) );
QCOMPARE( style.smartgroup( 3 ).values( QStringLiteral( "tag" ) ), QStringList() << QStringLiteral( "blue" ) );
QCOMPARE( style.smartgroup( 3 ).values( QStringLiteral( "!tag" ) ), QStringList() << QStringLiteral( "red" ) );
QCOMPARE( style.smartgroupId( QStringLiteral( "tags" ) ), 3 );
QCOMPARE( groupModifiedSpy.count(), 5 );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::SymbolEntity, 3 ), QStringList() << QStringLiteral( "symbolB" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::ColorrampEntity, 3 ), QStringList() << QStringLiteral( "ramp a" ) );
res = style.addSmartgroup( QStringLiteral( "combined" ), QStringLiteral( "AND" ), QStringList() << "blue", QStringList(), QStringList(), QStringList() << "a" );
QCOMPARE( res, 4 );
QCOMPARE( style.smartgroupNames(), QStringList() << QStringLiteral( "mine" ) << QStringLiteral( "tag" ) << QStringLiteral( "tags" ) << QStringLiteral( "combined" ) );
QCOMPARE( style.smartgroup( 4 ).values( QStringLiteral( "tag" ) ), QStringList() << QStringLiteral( "blue" ) );
QCOMPARE( style.smartgroup( 4 ).values( QStringLiteral( "!name" ) ), QStringList() << QStringLiteral( "a" ) );
QCOMPARE( style.smartgroupId( QStringLiteral( "combined" ) ), 4 );
QCOMPARE( groupModifiedSpy.count(), 6 );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::SymbolEntity, 4 ), QStringList() << QStringLiteral( "symbolB" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::ColorrampEntity, 4 ), QStringList() << QStringLiteral( "different bbb" ) );
style.remove( QgsStyle::SmartgroupEntity, 1 );
QCOMPARE( style.smartgroupNames(), QStringList() << QStringLiteral( "tag" ) << QStringLiteral( "tags" ) << QStringLiteral( "combined" ) );
QCOMPARE( groupModifiedSpy.count(), 7 );
style.remove( QgsStyle::SmartgroupEntity, 4 );
QCOMPARE( style.smartgroupNames(), QStringList() << QStringLiteral( "tag" ) << QStringLiteral( "tags" ) );
QCOMPARE( groupModifiedSpy.count(), 8 );
}
QGSTEST_MAIN( TestStyle )
#include "testqgsstyle.moc"