Partial 3d symbol style manager work

This commit is contained in:
Nyall Dawson 2020-07-14 19:38:04 +10:00
parent 0ec238b703
commit 6e40962510
12 changed files with 771 additions and 1 deletions

View File

@ -96,6 +96,7 @@ Constructor for QgsStyle.
TextFormatEntity,
LabelSettingsEntity,
LegendPatchShapeEntity,
Symbol3DEntity,
};
bool addEntity( const QString &name, const QgsStyleEntityInterface *entity, bool update = false );
@ -186,6 +187,21 @@ Returns ``True`` if the operation was successful.
Adding legend patch shapes with the name of existing ones replaces them.
.. versionadded:: 3.14
%End
bool addSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol /Transfer/, bool update = false );
%Docstring
Adds a 3d ``symbol`` with the specified ``name`` to the style. Ownership of ``symbol`` is transferred.
If ``update`` is set to ``True``, the style database will be automatically updated with the new legend patch shape.
Returns ``True`` if the operation was successful.
.. note::
Adding 3d symbols with the name of existing ones replaces them.
.. versionadded:: 3.16
%End
int addTag( const QString &tagName );
@ -311,6 +327,29 @@ with the specified ``name``, or QgsSymbol.Hybrid
if a matching legend patch shape is not present.
.. versionadded:: 3.14
%End
QgsAbstract3DSymbol *symbol3D( const QString &name ) const /Factory/;
%Docstring
Returns a new copy of the 3D symbol with the specified ``name``.
.. versionadded:: 3.16
%End
int symbol3DCount() const;
%Docstring
Returns count of 3D symbols in the style.
.. versionadded:: 3.16
%End
QString symbol3DType( const QString &name ) const;
%Docstring
Returns the symbol type corresponding to the 3d symbol
with the specified ``name``, or an empty string
if a matching 3d symbol is not present.
.. versionadded:: 3.16
%End
QgsWkbTypes::GeometryType labelSettingsLayerType( const QString &name ) const;
@ -663,6 +702,34 @@ Returns the default patch geometry for the given symbol ``type`` and ``size`` as
.. seealso:: :py:func:`defaultPatch`
.. versionadded:: 3.14
%End
bool saveSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol /Transfer/, bool favorite, const QStringList &tags );
%Docstring
Adds a 3d ``symbol`` to the database.
:param name: is the name of the 3d symbol
:param symbol: 3d symbol to save. Ownership is transferred.
:param favorite: is a boolean value to specify whether the 3d symbol should be added to favorites
:param tags: is a list of tags that are associated with the 3d symbol
:return: returns the success state of the save operation
.. versionadded:: 3.16
%End
bool renameSymbol3D( const QString &oldName, const QString &newName );
%Docstring
Changes a 3d symbol's name.
.. versionadded:: 3.16
%End
QStringList symbol3DNames() const;
%Docstring
Returns a list of names of 3d symbols in the style.
.. versionadded:: 3.16
%End
bool createDatabase( const QString &filename );
@ -1241,6 +1308,36 @@ Returns the entity's legend patch shape.
};
class QgsStyleSymbol3DEntity : QgsStyleEntityInterface
{
%Docstring
A 3d symbol entity for QgsStyle databases.
.. versionadded:: 3.16
%End
%TypeHeaderCode
#include "qgsstyle.h"
%End
public:
QgsStyleSymbol3DEntity( const QgsAbstract3DSymbol *symbol );
%Docstring
Constructor for QgsStyleSymbol3DEntity, with the specified ``symbol``.
Ownership of ``symbol`` is NOT transferred.
%End
virtual QgsStyle::StyleEntity type() const;
const QgsAbstract3DSymbol *symbol() const;
%Docstring
Returns the entity's symbol.
%End
};
/************************************************************************
* This file has been generated automatically from *
* *

View File

@ -29,6 +29,8 @@
#include "qgslinesymbollayer.h"
#include "qgsfillsymbollayer.h"
#include "qgsruntimeprofiler.h"
#include "qgsabstract3dsymbol.h"
#include "qgs3dsymbolregistry.h"
#include <QDomDocument>
#include <QDomElement>
@ -54,6 +56,17 @@ enum LegendPatchTable
LegendPatchTableFavoriteId, //!< Legend patch is favorite flag
};
/**
* Columns available in the 3d symbol table.
*/
enum Symbol3DTable
{
Symbol3DTableId, //!< 3d symbol ID
Symbol3DTableName, //!< 3d symbol name
Symbol3DTableXML, //!< 3d symbol definition (as XML)
Symbol3DTableFavoriteId, //!< 3d symbol is favorite flag
};
QgsStyle *QgsStyle::sDefaultStyle = nullptr;
@ -100,6 +113,9 @@ bool QgsStyle::addEntity( const QString &name, const QgsStyleEntityInterface *en
case LegendPatchShapeEntity:
return addLegendPatchShape( name, static_cast< const QgsStyleLegendPatchShapeEntity * >( entity )->shape(), update );
case Symbol3DEntity:
return addSymbol3D( name, static_cast< const QgsStyleSymbol3DEntity * >( entity )->symbol()->clone(), update );
case TagEntity:
case SmartgroupEntity:
break;
@ -147,10 +163,12 @@ void QgsStyle::clear()
{
qDeleteAll( mSymbols );
qDeleteAll( mColorRamps );
qDeleteAll( m3dSymbols );
mSymbols.clear();
mColorRamps.clear();
mTextFormats.clear();
m3dSymbols.clear();
mCachedTags.clear();
mCachedFavorites.clear();
@ -238,6 +256,9 @@ bool QgsStyle::renameEntity( QgsStyle::StyleEntity type, const QString &oldName,
case LegendPatchShapeEntity:
return renameLegendPatchShape( oldName, newName );
case Symbol3DEntity:
return renameSymbol3D( oldName, newName );
case TagEntity:
case SmartgroupEntity:
return false;
@ -354,6 +375,27 @@ bool QgsStyle::addLegendPatchShape( const QString &name, const QgsLegendPatchSha
return true;
}
bool QgsStyle::addSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol, bool update )
{
// delete previous symbol (if any)
if ( m3dSymbols.contains( name ) )
{
// TODO remove groups and tags?
delete m3dSymbols.take( name );
m3dSymbols.insert( name, symbol );
if ( update )
updateSymbol( Symbol3DEntity, name );
}
else
{
m3dSymbols.insert( name, symbol );
if ( update )
saveSymbol3D( name, symbol, false, QStringList() );
}
return true;
}
bool QgsStyle::saveColorRamp( const QString &name, QgsColorRamp *ramp, bool favorite, const QStringList &tags )
{
// insert it into the database
@ -483,6 +525,11 @@ void QgsStyle::createTables()
"name TEXT UNIQUE,"\
"xml TEXT,"\
"favorite INTEGER);"\
"CREATE TABLE symbol3d("\
"id INTEGER PRIMARY KEY,"\
"name TEXT UNIQUE,"\
"xml TEXT,"\
"favorite INTEGER);"\
"CREATE TABLE tag("\
"id INTEGER PRIMARY KEY,"\
"name TEXT);"\
@ -501,6 +548,9 @@ void QgsStyle::createTables()
"CREATE TABLE lpstagmap("\
"tag_id INTEGER NOT NULL,"\
"legendpatchshape_id INTEGER);"\
"CREATE TABLE symbol3dtagmap("\
"tag_id INTEGER NOT NULL,"\
"symbol3d_id INTEGER);"\
"CREATE TABLE smartgroup("\
"id INTEGER PRIMARY KEY,"\
"name TEXT,"\
@ -567,6 +617,21 @@ bool QgsStyle::load( const QString &filename )
"legendpatchshape_id INTEGER);" );
runEmptyQuery( query );
}
// make sure 3d symbol table exists
query = QgsSqlite3Mprintf( "SELECT name FROM sqlite_master WHERE name='symbol3d'" );
statement = mCurrentDB.prepare( query, rc );
if ( rc != SQLITE_OK || sqlite3_step( statement.get() ) != SQLITE_ROW )
{
query = QgsSqlite3Mprintf( "CREATE TABLE symbol3d("\
"id INTEGER PRIMARY KEY,"\
"name TEXT UNIQUE,"\
"xml TEXT,"\
"favorite INTEGER);"\
"CREATE TABLE symbol3dtagmap("\
"tag_id INTEGER NOT NULL,"\
"symbol3d_id INTEGER);" );
runEmptyQuery( query );
}
// Make sure there are no Null fields in parenting symbols and groups
query = QgsSqlite3Mprintf( "UPDATE symbol SET favorite=0 WHERE favorite IS NULL;"
@ -574,6 +639,7 @@ bool QgsStyle::load( const QString &filename )
"UPDATE textformat SET favorite=0 WHERE favorite IS NULL;"
"UPDATE labelsettings SET favorite=0 WHERE favorite IS NULL;"
"UPDATE legendpatchshapes SET favorite=0 WHERE favorite IS NULL;"
"UPDATE symbol3d SET favorite=0 WHERE favorite IS NULL;"
);
runEmptyQuery( query );
@ -690,6 +756,38 @@ bool QgsStyle::load( const QString &filename )
}
}
{
QgsScopedRuntimeProfile profile( tr( "Load 3d symbols shapes" ) );
query = QgsSqlite3Mprintf( "SELECT * FROM symbol3d" );
statement = mCurrentDB.prepare( query, rc );
while ( rc == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
{
QDomDocument doc;
const QString settingsName = statement.columnAsText( Symbol3DTableName );
QgsScopedRuntimeProfile profile( settingsName );
const QString xmlstring = statement.columnAsText( Symbol3DTableXML );
if ( !doc.setContent( xmlstring ) )
{
QgsDebugMsg( "Cannot open 3d symbol " + settingsName );
continue;
}
QDomElement settingsElement = doc.documentElement();
const QString symbolType = settingsElement.attribute( QStringLiteral( "type" ) );
std::unique_ptr< QgsAbstract3DSymbol > symbol( QgsApplication::symbol3DRegistry()->createSymbol( symbolType ) );
if ( symbol )
{
symbol->readXml( settingsElement, QgsReadWriteContext() );
m3dSymbols.insert( settingsName, symbol.release() );
}
else
{
QgsDebugMsg( "Cannot open 3d symbol " + settingsName );
continue;
}
}
}
mFileName = filename;
return true;
}
@ -1082,6 +1180,74 @@ QList<QList<QPolygonF> > QgsStyle::defaultPatchAsQPolygonF( QgsSymbol::SymbolTyp
return res;
}
bool QgsStyle::saveSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol, bool favorite, const QStringList &tags )
{
// insert it into the database
QDomDocument doc( QStringLiteral( "dummy" ) );
QDomElement elem = doc.createElement( QStringLiteral( "symbol" ) );
elem.setAttribute( QStringLiteral( "type" ), symbol->type() );
symbol->writeXml( elem, QgsReadWriteContext() );
QByteArray xmlArray;
QTextStream stream( &xmlArray );
stream.setCodec( "UTF-8" );
elem.save( stream, 4 );
auto query = QgsSqlite3Mprintf( "INSERT INTO symbol3d VALUES (NULL, '%q', '%q', %d);",
name.toUtf8().constData(), xmlArray.constData(), ( favorite ? 1 : 0 ) );
if ( !runEmptyQuery( query ) )
{
QgsDebugMsg( QStringLiteral( "Couldn't insert 3d symbol into the database!" ) );
return false;
}
mCachedFavorites[ Symbol3DEntity ].insert( name, favorite );
tagSymbol( Symbol3DEntity, name, tags );
emit entityAdded( Symbol3DEntity, name );
return true;
}
bool QgsStyle::renameSymbol3D( const QString &oldName, const QString &newName )
{
if ( m3dSymbols.contains( newName ) )
{
QgsDebugMsg( QStringLiteral( "3d symbol of new name already exists." ) );
return false;
}
if ( !m3dSymbols.contains( oldName ) )
return false;
QgsAbstract3DSymbol *symbol = m3dSymbols.take( oldName );
m3dSymbols.insert( newName, symbol );
mCachedTags[Symbol3DEntity ].remove( oldName );
mCachedFavorites[ Symbol3DEntity ].remove( oldName );
int labelSettingsId = 0;
sqlite3_statement_unique_ptr statement;
auto query = QgsSqlite3Mprintf( "SELECT id FROM symbol3d WHERE name='%q'", oldName.toUtf8().constData() );
int nErr;
statement = mCurrentDB.prepare( query, nErr );
if ( nErr == SQLITE_OK && sqlite3_step( statement.get() ) == SQLITE_ROW )
{
labelSettingsId = sqlite3_column_int( statement.get(), 0 );
}
const bool result = rename( Symbol3DEntity, labelSettingsId, newName );
if ( result )
{
emit entityRenamed( Symbol3DEntity, oldName, newName );
}
return result;
}
QStringList QgsStyle::symbol3DNames() const
{
return m3dSymbols.keys();
}
QStringList QgsStyle::symbolsOfFavorite( StyleEntity type ) const
{
if ( !mCurrentDB )
@ -1300,6 +1466,15 @@ bool QgsStyle::removeEntityByName( QgsStyle::StyleEntity type, const QString &na
break;
}
case QgsStyle::Symbol3DEntity:
{
std::unique_ptr< QgsAbstract3DSymbol > symbol( m3dSymbols.take( name ) );
if ( !symbol )
return false;
break;
}
case QgsStyle::ColorrampEntity:
{
std::unique_ptr< QgsColorRamp > ramp( mColorRamps.take( name ) );
@ -1935,6 +2110,24 @@ QgsSymbol::SymbolType QgsStyle::legendPatchShapeSymbolType( const QString &name
return mLegendPatchShapes.value( name ).symbolType();
}
QgsAbstract3DSymbol *QgsStyle::symbol3D( const QString &name ) const
{
return m3dSymbols.contains( name ) ? m3dSymbols.value( name )->clone() : nullptr;
}
int QgsStyle::symbol3DCount() const
{
return m3dSymbols.count();
}
QString QgsStyle::symbol3DType( const QString &name ) const
{
if ( !m3dSymbols.contains( name ) )
return QString();
return m3dSymbols.value( name )->type();
}
QgsWkbTypes::GeometryType QgsStyle::labelSettingsLayerType( const QString &name ) const
{
if ( !mLabelSettings.contains( name ) )
@ -2011,6 +2204,9 @@ QStringList QgsStyle::allNames( QgsStyle::StyleEntity type ) const
case LegendPatchShapeEntity:
return legendPatchShapeNames();
case Symbol3DEntity:
return symbol3DNames();
case TagEntity:
return tags();
@ -2299,6 +2495,8 @@ bool QgsStyle::exportXml( const QString &filename )
const QStringList favoriteSymbols = symbolsOfFavorite( SymbolEntity );
const QStringList favoriteColorramps = symbolsOfFavorite( ColorrampEntity );
const QStringList favoriteTextFormats = symbolsOfFavorite( TextFormatEntity );
const QStringList favoriteLegendShapes = symbolsOfFavorite( LegendPatchShapeEntity );
const QStringList favorite3DSymbols = symbolsOfFavorite( Symbol3DEntity );
// save symbols and attach tags
QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( mSymbols, QStringLiteral( "symbols" ), doc, QgsReadWriteContext() );
@ -2390,18 +2588,41 @@ bool QgsStyle::exportXml( const QString &filename )
{
legendPatchShapeEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
}
if ( favoriteTextFormats.contains( it.key() ) )
if ( favoriteLegendShapes.contains( it.key() ) )
{
legendPatchShapeEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
}
legendPatchShapesElem.appendChild( legendPatchShapeEl );
}
// save symbols and attach tags
QDomElement symbols3DElem = doc.createElement( QStringLiteral( "symbols3d" ) );
for ( auto it = m3dSymbols.constBegin(); it != m3dSymbols.constEnd(); ++it )
{
QDomElement symbolEl = doc.createElement( QStringLiteral( "symbol3d" ) );
symbolEl.setAttribute( QStringLiteral( "name" ), it.key() );
QDomElement defEl = doc.createElement( QStringLiteral( "definition" ) );
defEl.setAttribute( QStringLiteral( "type" ), it.value()->type() );
it.value()->writeXml( defEl, QgsReadWriteContext() );
symbolEl.appendChild( defEl );
QStringList tags = tagsOfSymbol( Symbol3DEntity, it.key() );
if ( tags.count() > 0 )
{
symbolEl.setAttribute( QStringLiteral( "tags" ), tags.join( ',' ) );
}
if ( favorite3DSymbols.contains( it.key() ) )
{
symbolEl.setAttribute( QStringLiteral( "favorite" ), QStringLiteral( "1" ) );
}
symbols3DElem.appendChild( symbolEl );
}
root.appendChild( symbolsElem );
root.appendChild( rampsElem );
root.appendChild( textFormatsElem );
root.appendChild( labelSettingsElem );
root.appendChild( legendPatchShapesElem );
root.appendChild( symbols3DElem );
// save
QFile f( filename );
@ -2702,6 +2923,56 @@ bool QgsStyle::importXml( const QString &filename, int sinceVersion )
}
}
// load 3d symbols
if ( version == STYLE_CURRENT_VERSION )
{
const QDomElement symbols3DElement = docEl.firstChildElement( QStringLiteral( "symbols3d" ) );
e = symbols3DElement.firstChildElement();
while ( !e.isNull() )
{
const int entityAddedVersion = e.attribute( QStringLiteral( "addedVersion" ) ).toInt();
if ( entityAddedVersion != 0 && sinceVersion != -1 && entityAddedVersion <= sinceVersion )
{
// skip the symbol, should already be present
continue;
}
if ( e.tagName() == QLatin1String( "symbol3d" ) )
{
QString name = e.attribute( QStringLiteral( "name" ) );
QStringList tags;
if ( e.hasAttribute( QStringLiteral( "tags" ) ) )
{
tags = e.attribute( QStringLiteral( "tags" ) ).split( ',' );
}
bool favorite = false;
if ( e.hasAttribute( QStringLiteral( "favorite" ) ) && e.attribute( QStringLiteral( "favorite" ) ) == QStringLiteral( "1" ) )
{
favorite = true;
}
const QDomElement symbolElem = e.firstChildElement();
const QString type = symbolElem.attribute( QStringLiteral( "type" ) );
std::unique_ptr< QgsAbstract3DSymbol > sym( QgsApplication::symbol3DRegistry()->createSymbol( type ) );
if ( sym )
{
sym->readXml( symbolElem, QgsReadWriteContext() );
QgsAbstract3DSymbol *newSym = sym.get();
addSymbol3D( name, sym.release() );
if ( mCurrentDB )
{
saveSymbol3D( name, newSym, favorite, tags );
}
}
}
else
{
QgsDebugMsg( "unknown tag: " + e.tagName() );
}
e = e.nextSiblingElement();
}
}
query = QgsSqlite3Mprintf( "COMMIT TRANSACTION;" );
runEmptyQuery( query );
@ -2762,6 +3033,29 @@ bool QgsStyle::updateSymbol( StyleEntity type, const QString &name )
break;
}
case Symbol3DEntity:
{
// check if it is an existing symbol
if ( !symbol3DNames().contains( name ) )
{
QgsDebugMsg( QStringLiteral( "Update request received for unavailable symbol" ) );
return false;
}
symEl = doc.createElement( QStringLiteral( "symbol" ) );
symEl.setAttribute( QStringLiteral( "type" ), m3dSymbols.value( name )->type() );
m3dSymbols.value( name )->writeXml( symEl, QgsReadWriteContext() );
if ( symEl.isNull() )
{
QgsDebugMsg( QStringLiteral( "Couldn't convert symbol to valid XML!" ) );
return false;
}
symEl.save( stream, 4 );
query = QgsSqlite3Mprintf( "UPDATE symbol3d SET xml='%q' WHERE name='%q';",
xmlArray.constData(), name.toUtf8().constData() );
break;
}
case ColorrampEntity:
{
if ( !colorRampNames().contains( name ) )
@ -2879,6 +3173,7 @@ bool QgsStyle::updateSymbol( StyleEntity type, const QString &name )
case LegendPatchShapeEntity:
case TagEntity:
case SmartgroupEntity:
case Symbol3DEntity:
break;
}
emit entityChanged( type, name );
@ -2953,6 +3248,9 @@ QString QgsStyle::entityTableName( QgsStyle::StyleEntity type )
case LegendPatchShapeEntity:
return QStringLiteral( "legendpatchshapes" );
case Symbol3DEntity:
return QStringLiteral( "symbol3d" );
case TagEntity:
return QStringLiteral( "tag" );
@ -2981,6 +3279,9 @@ QString QgsStyle::tagmapTableName( QgsStyle::StyleEntity type )
case LegendPatchShapeEntity:
return QStringLiteral( "lpstagmap" );
case Symbol3DEntity:
return QStringLiteral( "symbol3dtagmap" );
case TagEntity:
case SmartgroupEntity:
break;
@ -3007,6 +3308,9 @@ QString QgsStyle::tagmapEntityIdFieldName( QgsStyle::StyleEntity type )
case LegendPatchShapeEntity:
return QStringLiteral( "legendpatchshape_id" );
case Symbol3DEntity:
return QStringLiteral( "symbol3d_id" );
case TagEntity:
case SmartgroupEntity:
break;
@ -3038,3 +3342,8 @@ QgsStyle::StyleEntity QgsStyleLegendPatchShapeEntity::type() const
{
return QgsStyle::LegendPatchShapeEntity;
}
QgsStyle::StyleEntity QgsStyleSymbol3DEntity::type() const
{
return QgsStyle::Symbol3DEntity;
}

View File

@ -184,6 +184,7 @@ class CORE_EXPORT QgsStyle : public QObject
TextFormatEntity, //!< Text formats
LabelSettingsEntity, //!< Label settings
LegendPatchShapeEntity, //!< Legend patch shape (since QGIS 3.14)
Symbol3DEntity, //!< 3D symbol entity (since QGIS 3.14)
};
/**
@ -256,6 +257,18 @@ class CORE_EXPORT QgsStyle : public QObject
*/
bool addLegendPatchShape( const QString &name, const QgsLegendPatchShape &shape, bool update = false );
/**
* Adds a 3d \a symbol with the specified \a name to the style. Ownership of \a symbol is transferred.
*
* If \a update is set to TRUE, the style database will be automatically updated with the new legend patch shape.
*
* Returns TRUE if the operation was successful.
*
* \note Adding 3d symbols with the name of existing ones replaces them.
* \since QGIS 3.16
*/
bool addSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol SIP_TRANSFER, bool update = false );
/**
* Adds a new tag and returns the tag's id
*
@ -378,6 +391,28 @@ class CORE_EXPORT QgsStyle : public QObject
*/
QgsSymbol::SymbolType legendPatchShapeSymbolType( const QString &name ) const;
/**
* Returns a new copy of the 3D symbol with the specified \a name.
*
* \since QGIS 3.16
*/
QgsAbstract3DSymbol *symbol3D( const QString &name ) const SIP_FACTORY;
/**
* Returns count of 3D symbols in the style.
* \since QGIS 3.16
*/
int symbol3DCount() const;
/**
* Returns the symbol type corresponding to the 3d symbol
* with the specified \a name, or an empty string
* if a matching 3d symbol is not present.
*
* \since QGIS 3.16
*/
QString symbol3DType( const QString &name ) const;
/**
* Returns the layer geometry type corresponding to the label settings
* with the specified \a name, or QgsWkbTypes::UnknownGeometry
@ -688,6 +723,32 @@ class CORE_EXPORT QgsStyle : public QObject
*/
QList< QList< QPolygonF > > defaultPatchAsQPolygonF( QgsSymbol::SymbolType type, QSizeF size ) const;
/**
* Adds a 3d \a symbol to the database.
*
* \param name is the name of the 3d symbol
* \param symbol 3d symbol to save. Ownership is transferred.
* \param favorite is a boolean value to specify whether the 3d symbol should be added to favorites
* \param tags is a list of tags that are associated with the 3d symbol
* \returns returns the success state of the save operation
*
* \since QGIS 3.16
*/
bool saveSymbol3D( const QString &name, QgsAbstract3DSymbol *symbol SIP_TRANSFER, bool favorite, const QStringList &tags );
/**
* Changes a 3d symbol's name.
*
* \since QGIS 3.16
*/
bool renameSymbol3D( const QString &oldName, const QString &newName );
/**
* Returns a list of names of 3d symbols in the style.
* \since QGIS 3.16
*/
QStringList symbol3DNames() const;
/**
* Creates an on-disk database
*
@ -1006,6 +1067,7 @@ class CORE_EXPORT QgsStyle : public QObject
QgsTextFormatMap mTextFormats;
QgsLabelSettingsMap mLabelSettings;
QMap<QString, QgsLegendPatchShape > mLegendPatchShapes;
QMap<QString, QgsAbstract3DSymbol * > m3dSymbols;
QHash< QgsStyle::StyleEntity, QHash< QString, QStringList > > mCachedTags;
QHash< QgsStyle::StyleEntity, QHash< QString, bool > > mCachedFavorites;
@ -1278,4 +1340,35 @@ class CORE_EXPORT QgsStyleLegendPatchShapeEntity : public QgsStyleEntityInterfac
QgsLegendPatchShape mShape;
};
/**
* \class QgsStyleSymbol3DEntity
* \ingroup core
* A 3d symbol entity for QgsStyle databases.
* \since QGIS 3.16
*/
class CORE_EXPORT QgsStyleSymbol3DEntity : public QgsStyleEntityInterface
{
public:
/**
* Constructor for QgsStyleSymbol3DEntity, with the specified \a symbol.
*
* Ownership of \a symbol is NOT transferred.
*/
QgsStyleSymbol3DEntity( const QgsAbstract3DSymbol *symbol )
: mSymbol( symbol )
{}
QgsStyle::StyleEntity type() const override;
/**
* Returns the entity's symbol.
*/
const QgsAbstract3DSymbol *symbol() const { return mSymbol; }
private:
const QgsAbstract3DSymbol *mSymbol = nullptr;
};
#endif

View File

@ -175,6 +175,7 @@ QVariant QgsStyleModel::data( const QModelIndex &index, int role ) const
case QgsStyle::ColorrampEntity:
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
case QgsStyle::Symbol3DEntity:
break;
}
return tooltip;
@ -317,6 +318,7 @@ QVariant QgsStyleModel::data( const QModelIndex &index, int role ) const
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
case QgsStyle::Symbol3DEntity:
return QVariant();
}
break;
@ -354,6 +356,7 @@ QVariant QgsStyleModel::data( const QModelIndex &index, int role ) const
case QgsStyle::SmartgroupEntity:
case QgsStyle::LabelSettingsEntity:
case QgsStyle::TextFormatEntity:
case QgsStyle::Symbol3DEntity:
return QVariant();
}
return QVariant();

View File

@ -1256,6 +1256,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_SOURCE_DIR}/src/gui/vectortile
${CMAKE_SOURCE_DIR}/src/gui/tableeditor
${CMAKE_SOURCE_DIR}/src/core
${CMAKE_SOURCE_DIR}/src/core/3d
${CMAKE_SOURCE_DIR}/src/core/annotations
${CMAKE_SOURCE_DIR}/src/core/auth
${CMAKE_SOURCE_DIR}/src/core/callouts

View File

@ -681,6 +681,7 @@ void QgsLabelingGui::setFormatFromStyle( const QString &name, QgsStyle::StyleEnt
case QgsStyle::SmartgroupEntity:
case QgsStyle::TextFormatEntity:
case QgsStyle::LegendPatchShapeEntity:
case QgsStyle::Symbol3DEntity:
{
QgsTextFormatWidget::setFormatFromStyle( name, type );
return;
@ -781,6 +782,7 @@ void QgsLabelingGui::saveFormat()
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
case QgsStyle::LegendPatchShapeEntity:
case QgsStyle::Symbol3DEntity:
break;
}
}

View File

@ -204,6 +204,13 @@ void QgsStyleItemsListWidget::setEntityType( QgsStyle::StyleEntity type )
groupsCombo->setItemText( allGroup, tr( "All Legend Patch Shapes" ) );
break;
case QgsStyle::Symbol3DEntity:
btnSaveSymbol->setText( tr( "Save 3D Symbol…" ) );
btnSaveSymbol->setToolTip( tr( "Save 3D symbol to styles" ) );
if ( allGroup >= 0 )
groupsCombo->setItemText( allGroup, tr( "All 3D Symbols" ) );
break;
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
break;
@ -330,6 +337,10 @@ void QgsStyleItemsListWidget::populateGroups()
allText = tr( "All Legend Patch Shapes" );
break;
case QgsStyle::Symbol3DEntity:
allText = tr( "All 3D Symbols" );
break;
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
break;

View File

@ -1814,6 +1814,7 @@ void QgsTextFormatWidget::setFormatFromStyle( const QString &name, QgsStyle::Sty
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
case QgsStyle::LegendPatchShapeEntity:
case QgsStyle::Symbol3DEntity:
return;
case QgsStyle::TextFormatEntity:

View File

@ -34,6 +34,7 @@
#include "qgstextformatwidget.h"
#include "qgslabelinggui.h"
#include "qgslegendpatchshapewidget.h"
#include "qgsabstract3dsymbol.h"
#include <QAction>
#include <QFile>
#include <QFileDialog>
@ -614,6 +615,7 @@ void QgsStyleManagerDialog::copyItem()
case QgsStyle::ColorrampEntity:
case QgsStyle::LegendPatchShapeEntity:
case QgsStyle::Symbol3DEntity:
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
return;
@ -1014,6 +1016,59 @@ int QgsStyleManagerDialog::copyItems( const QList<QgsStyleManagerDialog::ItemDet
break;
}
case QgsStyle::Symbol3DEntity:
{
std::unique_ptr< QgsAbstract3DSymbol > symbol( src->symbol3D( details.name ) );
if ( !symbol )
continue;
const bool hasDuplicateName = dst->symbol3DNames().contains( details.name );
bool overwriteThis = false;
if ( isImport )
addItemToFavorites = favoriteSymbols.contains( details.name );
if ( hasDuplicateName && prompt )
{
cursorOverride.reset();
int res = QMessageBox::warning( parentWidget, isImport ? tr( "Import 3D Symbol" ) : tr( "Export 3D Symbol" ),
tr( "A 3D symbol with the name “%1” already exists.\nOverwrite?" )
.arg( details.name ),
QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll | QMessageBox::Cancel );
cursorOverride = qgis::make_unique< QgsTemporaryCursorOverride >( Qt::WaitCursor );
switch ( res )
{
case QMessageBox::Cancel:
return count;
case QMessageBox::No:
continue;
case QMessageBox::Yes:
overwriteThis = true;
break;
case QMessageBox::YesToAll:
prompt = false;
overwriteAll = true;
break;
case QMessageBox::NoToAll:
prompt = false;
overwriteAll = false;
break;
}
}
if ( !hasDuplicateName || overwriteAll || overwriteThis )
{
QgsAbstract3DSymbol *newSymbol = symbol.get();
dst->addSymbol3D( details.name, symbol.release() );
dst->saveSymbol3D( details.name, newSymbol, addItemToFavorites, symbolTags );
count++;
}
break;
}
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
break;

View File

@ -65,6 +65,11 @@ QgsStyleSaveDialog::QgsStyleSaveDialog( QWidget *parent, QgsStyle::StyleEntity t
possibleEntities << QgsStyle::LegendPatchShapeEntity;
break;
case QgsStyle::Symbol3DEntity:
this->setWindowTitle( tr( "Save New 3D Symbol" ) );
possibleEntities << QgsStyle::Symbol3DEntity;
break;
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
break;
@ -101,6 +106,10 @@ QgsStyleSaveDialog::QgsStyleSaveDialog( QWidget *parent, QgsStyle::StyleEntity t
mComboSaveAs->addItem( QgsApplication::getThemeIcon( QStringLiteral( "legend.svg" ) ), tr( "Legend Patch Shape" ), e );
break;
case QgsStyle::Symbol3DEntity:
mComboSaveAs->addItem( QgsApplication::getThemeIcon( QStringLiteral( "3d.svg" ) ), tr( "3D Symbol" ), e );
break;
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
break;

View File

@ -7,6 +7,7 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/external/kdbush/include
${CMAKE_SOURCE_DIR}/external/nlohmann
${CMAKE_SOURCE_DIR}/src/core
${CMAKE_SOURCE_DIR}/src/core/3d
${CMAKE_SOURCE_DIR}/src/core/annotations
${CMAKE_SOURCE_DIR}/src/core/auth
${CMAKE_SOURCE_DIR}/src/core/callouts

View File

@ -50,6 +50,8 @@
#include "qgslayertreelayer.h"
#include "qgslayertreeutils.h"
#include "qgsmaplayerlegend.h"
#include "qgsabstract3dsymbol.h"
#include "qgs3dsymbolregistry.h"
/**
* \ingroup UnitTests
@ -86,6 +88,7 @@ class TestStyle : public QObject
void testCreateTextFormats();
void testCreateLabelSettings();
void testCreateLegendPatchShapes();
void testCreate3dSymbol();
void testLoadColorRamps();
void testSaveLoad();
void testFavorites();
@ -96,6 +99,21 @@ class TestStyle : public QObject
};
class Dummy3DSymbol : public QgsAbstract3DSymbol
{
public:
static QgsAbstract3DSymbol *create() { return new Dummy3DSymbol; }
QString type() const override { return QStringLiteral( "dummy" ); }
QgsAbstract3DSymbol *clone() const override { Dummy3DSymbol *res = new Dummy3DSymbol(); res->id = id; return res; }
void readXml( const QDomElement &elem, const QgsReadWriteContext & ) override { id = elem.attribute( QStringLiteral( "id" ) ); }
void writeXml( QDomElement &elem, const QgsReadWriteContext & ) const override { elem.setAttribute( QStringLiteral( "id" ), id ); }
QString id;
};
TestStyle::TestStyle() = default;
// slots
@ -123,6 +141,9 @@ void TestStyle::initTestCase()
QgsCptCityArchive::initArchives();
mReport += QLatin1String( "<h1>Style Tests</h1>\n" );
QgsApplication::symbol3DRegistry()->addSymbolType( new Qgs3DSymbolMetadata( QStringLiteral( "dummy" ), QObject::tr( "Dummy" ),
&Dummy3DSymbol::create, nullptr, nullptr ) );
}
void TestStyle::cleanupTestCase()
@ -417,6 +438,68 @@ void TestStyle::testCreateLegendPatchShapes()
QVERIFY( mStyle->legendPatchShapeNames().contains( QStringLiteral( "test_settings2" ) ) );
}
void TestStyle::testCreate3dSymbol()
{
QVERIFY( mStyle->symbol3DNames().isEmpty() );
QCOMPARE( mStyle->symbol3DCount(), 0 );
// non existent settings, should be default
QVERIFY( !mStyle->symbol3D( QString( "blah" ) ) );
QSignalSpy spy( mStyle, &QgsStyle::entityAdded );
QSignalSpy spyChanged( mStyle, &QgsStyle::entityChanged );
// add symbol
Dummy3DSymbol symbol;
symbol.id = QStringLiteral( "xxx" );
QVERIFY( mStyle->addSymbol3D( "test_settings", symbol.clone(), true ) );
QCOMPARE( spy.count(), 1 );
QCOMPARE( spyChanged.count(), 0 );
QVERIFY( mStyle->symbol3DNames().contains( QStringLiteral( "test_settings" ) ) );
QCOMPARE( mStyle->symbol3DCount(), 1 );
std::unique_ptr< Dummy3DSymbol > retrieved( dynamic_cast< Dummy3DSymbol * >( mStyle->symbol3D( QStringLiteral( "test_settings" ) ) ) );
QCOMPARE( retrieved->id, QStringLiteral( "xxx" ) );
symbol.id = QStringLiteral( "yyy" );
QVERIFY( mStyle->addSymbol3D( "test_settings", symbol.clone(), true ) );
QVERIFY( mStyle->symbol3DNames().contains( QStringLiteral( "test_settings" ) ) );
QCOMPARE( mStyle->symbol3DCount(), 1 );
retrieved.reset( dynamic_cast< Dummy3DSymbol * >( mStyle->symbol3D( QStringLiteral( "test_settings" ) ) ) );
QCOMPARE( retrieved->id, QStringLiteral( "yyy" ) );
QCOMPARE( spy.count(), 1 );
QCOMPARE( spyChanged.count(), 1 );
symbol.id = QStringLiteral( "zzz" );
QVERIFY( mStyle->addSymbol3D( "test_format2", symbol.clone(), true ) );
QVERIFY( mStyle->symbol3DNames().contains( QStringLiteral( "test_format2" ) ) );
QCOMPARE( mStyle->symbol3DCount(), 2 );
retrieved.reset( dynamic_cast< Dummy3DSymbol * >( mStyle->symbol3D( QStringLiteral( "test_settings" ) ) ) );
QCOMPARE( retrieved->id, QStringLiteral( "yyy" ) );
retrieved.reset( dynamic_cast< Dummy3DSymbol * >( mStyle->symbol3D( QStringLiteral( "test_format2" ) ) ) );
QCOMPARE( retrieved->id, QStringLiteral( "zzz" ) );
QCOMPARE( spy.count(), 2 );
QCOMPARE( spyChanged.count(), 1 );
// save and restore
QVERIFY( mStyle->exportXml( QDir::tempPath() + "/text_style.xml" ) );
QgsStyle style2;
QVERIFY( style2.importXml( QDir::tempPath() + "/text_style.xml" ) );
QVERIFY( style2.symbol3DNames().contains( QStringLiteral( "test_settings" ) ) );
QVERIFY( style2.symbol3DNames().contains( QStringLiteral( "test_format2" ) ) );
QCOMPARE( style2.symbol3DCount(), 2 );
retrieved.reset( dynamic_cast< Dummy3DSymbol * >( style2.symbol3D( QStringLiteral( "test_settings" ) ) ) );
QCOMPARE( retrieved->id, QStringLiteral( "yyy" ) );
retrieved.reset( dynamic_cast< Dummy3DSymbol * >( style2.symbol3D( QStringLiteral( "test_format2" ) ) ) );
QCOMPARE( retrieved->id, QStringLiteral( "zzz" ) );
QCOMPARE( mStyle->allNames( QgsStyle::Symbol3DEntity ), QStringList() << QStringLiteral( "test_format2" )
<< QStringLiteral( "test_settings" ) );
QgsStyleSymbol3DEntity entity( &symbol );
QVERIFY( mStyle->addEntity( "test_settings2", &entity, true ) );
QVERIFY( mStyle->symbol3DNames().contains( QStringLiteral( "test_settings2" ) ) );
}
void TestStyle::testLoadColorRamps()
{
QStringList colorRamps = mStyle->colorRampNames();
@ -499,6 +582,7 @@ void TestStyle::testFavorites()
QVERIFY( !mStyle->isFavorite( QgsStyle::LabelSettingsEntity, QStringLiteral( "AaaaaaaaaA" ) ) );
QVERIFY( !mStyle->isFavorite( QgsStyle::ColorrampEntity, QStringLiteral( "AaaaaaaaaA" ) ) );
QVERIFY( !mStyle->isFavorite( QgsStyle::LegendPatchShapeEntity, QStringLiteral( "AaaaaaaaaA" ) ) );
QVERIFY( !mStyle->isFavorite( QgsStyle::Symbol3DEntity, QStringLiteral( "AaaaaaaaaA" ) ) );
// add some symbols to favorites
std::unique_ptr< QgsMarkerSymbol > sym1( QgsMarkerSymbol::createSimple( QgsStringMap() ) );
@ -652,6 +736,32 @@ void TestStyle::testFavorites()
favorites = mStyle->symbolsOfFavorite( QgsStyle::LegendPatchShapeEntity );
QCOMPARE( favorites.count(), 0 );
QVERIFY( !mStyle->isFavorite( QgsStyle::LegendPatchShapeEntity, QStringLiteral( "settings_1" ) ) );
// symbol 3d
Dummy3DSymbol symbol3d1;
QVERIFY( mStyle->addSymbol3D( QStringLiteral( "settings_1" ), symbol3d1.clone(), true ) );
favorites = mStyle->symbolsOfFavorite( QgsStyle::Symbol3DEntity );
QCOMPARE( favorites.count(), 0 );
QVERIFY( !mStyle->isFavorite( QgsStyle::Symbol3DEntity, QStringLiteral( "settings_1" ) ) );
mStyle->addFavorite( QgsStyle::Symbol3DEntity, QStringLiteral( "settings_1" ) );
QCOMPARE( favoriteChangedSpy.count(), 11 );
QCOMPARE( favoriteChangedSpy.at( 10 ).at( 0 ).toInt(), static_cast< int >( QgsStyle::Symbol3DEntity ) );
QCOMPARE( favoriteChangedSpy.at( 10 ).at( 1 ).toString(), QStringLiteral( "settings_1" ) );
QCOMPARE( favoriteChangedSpy.at( 10 ).at( 2 ).toBool(), true );
favorites = mStyle->symbolsOfFavorite( QgsStyle::Symbol3DEntity );
QCOMPARE( favorites.count(), 1 );
QVERIFY( favorites.contains( QStringLiteral( "settings_1" ) ) );
QVERIFY( mStyle->isFavorite( QgsStyle::Symbol3DEntity, QStringLiteral( "settings_1" ) ) );
mStyle->removeFavorite( QgsStyle::Symbol3DEntity, QStringLiteral( "settings_1" ) );
QCOMPARE( favoriteChangedSpy.count(), 12 );
QCOMPARE( favoriteChangedSpy.at( 11 ).at( 0 ).toInt(), static_cast< int >( QgsStyle::Symbol3DEntity ) );
QCOMPARE( favoriteChangedSpy.at( 11 ).at( 1 ).toString(), QStringLiteral( "settings_1" ) );
QCOMPARE( favoriteChangedSpy.at( 11 ).at( 2 ).toBool(), false );
favorites = mStyle->symbolsOfFavorite( QgsStyle::Symbol3DEntity );
QCOMPARE( favorites.count(), 0 );
QVERIFY( !mStyle->isFavorite( QgsStyle::Symbol3DEntity, QStringLiteral( "settings_1" ) ) );
}
void TestStyle::testTags()
@ -1090,6 +1200,69 @@ void TestStyle::testTags()
QCOMPARE( tagsChangedSpy.at( 34 ).at( 0 ).toInt(), static_cast< int >( QgsStyle::LegendPatchShapeEntity ) );
QCOMPARE( tagsChangedSpy.at( 34 ).at( 1 ).toString(), QStringLiteral( "shape1" ) );
QCOMPARE( tagsChangedSpy.at( 34 ).at( 2 ).toStringList(), QStringList() );
// 3d symbols
// tag format
Dummy3DSymbol symbol3d1;
QVERIFY( mStyle->addSymbol3D( "3dsymbol1", symbol3d1.clone(), true ) );
Dummy3DSymbol symbol3d2;
QVERIFY( mStyle->addSymbol3D( "3dsymbol2", symbol3d2.clone(), true ) );
QVERIFY( mStyle->tagSymbol( QgsStyle::Symbol3DEntity, "3dsymbol1", QStringList() << "blue" << "starry" ) );
QCOMPARE( tagsChangedSpy.count(), 38 );
QCOMPARE( tagsChangedSpy.at( 37 ).at( 0 ).toInt(), static_cast< int>( QgsStyle::Symbol3DEntity ) );
QCOMPARE( tagsChangedSpy.at( 37 ).at( 1 ).toString(), QStringLiteral( "3dsymbol1" ) );
QCOMPARE( tagsChangedSpy.at( 37 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "blue" ) << QStringLiteral( "starry" ) );
QVERIFY( mStyle->tagSymbol( QgsStyle::Symbol3DEntity, "3dsymbol2", QStringList() << "red" << "circle" ) );
QCOMPARE( tagsChangedSpy.count(), 39 );
QCOMPARE( tagsChangedSpy.at( 38 ).at( 0 ).toInt(), static_cast< int>( QgsStyle::Symbol3DEntity ) );
QCOMPARE( tagsChangedSpy.at( 38 ).at( 1 ).toString(), QStringLiteral( "3dsymbol2" ) );
QCOMPARE( tagsChangedSpy.at( 38 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "red" ) << QStringLiteral( "circle" ) );
//bad format name
QVERIFY( !mStyle->tagSymbol( QgsStyle::Symbol3DEntity, "no patch", QStringList() << "red" << "circle" ) );
QCOMPARE( tagsChangedSpy.count(), 39 );
//tag which hasn't been added yet
QVERIFY( mStyle->tagSymbol( QgsStyle::Symbol3DEntity, "3dsymbol2", QStringList() << "red patch" ) );
QCOMPARE( tagsChangedSpy.count(), 40 );
QCOMPARE( tagsChangedSpy.at( 39 ).at( 0 ).toInt(), static_cast< int>( QgsStyle::Symbol3DEntity ) );
QCOMPARE( tagsChangedSpy.at( 39 ).at( 1 ).toString(), QStringLiteral( "3dsymbol2" ) );
QCOMPARE( tagsChangedSpy.at( 39 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "red" ) << QStringLiteral( "circle" ) << QStringLiteral( "red patch" ) );
tags = mStyle->tags();
QVERIFY( tags.contains( QStringLiteral( "red patch" ) ) );
//check that tags have been applied
tags = mStyle->tagsOfSymbol( QgsStyle::Symbol3DEntity, QStringLiteral( "3dsymbol1" ) );
QCOMPARE( tags.count(), 2 );
QVERIFY( tags.contains( "blue" ) );
QVERIFY( tags.contains( "starry" ) );
tags = mStyle->tagsOfSymbol( QgsStyle::Symbol3DEntity, QStringLiteral( "3dsymbol2" ) );
QCOMPARE( tags.count(), 3 );
QVERIFY( tags.contains( "red" ) );
QVERIFY( tags.contains( "circle" ) );
QVERIFY( tags.contains( "red patch" ) );
//remove a tag, including a non-present tag
QVERIFY( mStyle->detagSymbol( QgsStyle::Symbol3DEntity, "3dsymbol1", QStringList() << "bad" << "blue" ) );
tags = mStyle->tagsOfSymbol( QgsStyle::Symbol3DEntity, QStringLiteral( "3dsymbol1" ) );
QCOMPARE( tags.count(), 1 );
QVERIFY( tags.contains( "starry" ) );
QCOMPARE( tagsChangedSpy.count(), 41 );
QCOMPARE( tagsChangedSpy.at( 40 ).at( 0 ).toInt(), static_cast< int >( QgsStyle::Symbol3DEntity ) );
QCOMPARE( tagsChangedSpy.at( 40 ).at( 1 ).toString(), QStringLiteral( "3dsymbol1" ) );
QCOMPARE( tagsChangedSpy.at( 40 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "starry" ) );
// completely detag symbol
QVERIFY( mStyle->detagSymbol( QgsStyle::Symbol3DEntity, QStringLiteral( "3dsymbol1" ) ) );
tags = mStyle->tagsOfSymbol( QgsStyle::Symbol3DEntity, QStringLiteral( "3dsymbol1" ) );
QCOMPARE( tags.count(), 0 );
QCOMPARE( tagsChangedSpy.count(), 42 );
QCOMPARE( tagsChangedSpy.at( 41 ).at( 0 ).toInt(), static_cast< int >( QgsStyle::Symbol3DEntity ) );
QCOMPARE( tagsChangedSpy.at( 41 ).at( 1 ).toString(), QStringLiteral( "3dsymbol1" ) );
QCOMPARE( tagsChangedSpy.at( 41 ).at( 2 ).toStringList(), QStringList() );
}
void TestStyle::testSmartGroup()
@ -1125,6 +1298,11 @@ void TestStyle::testSmartGroup()
QgsLegendPatchShape shape2;
QVERIFY( style.addLegendPatchShape( "different shp bbb", shape2, true ) );
Dummy3DSymbol symbol3d1;
QVERIFY( style.addSymbol3D( "symbol3D a", symbol3d1.clone(), true ) );
Dummy3DSymbol symbol3d2;
QVERIFY( style.addSymbol3D( "different symbol3D bbb", symbol3d2.clone(), true ) );
QVERIFY( style.smartgroupNames().empty() );
QVERIFY( style.smartgroup( 5 ).isEmpty() );
QCOMPARE( style.smartgroupId( QStringLiteral( "no exist" ) ), 0 );
@ -1144,6 +1322,7 @@ void TestStyle::testSmartGroup()
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::TextFormatEntity, 1 ), QStringList() << QStringLiteral( "format a" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::LabelSettingsEntity, 1 ), QStringList() << QStringLiteral( "settings a" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::LegendPatchShapeEntity, 1 ), QStringList() << QStringLiteral( "shp a" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::Symbol3DEntity, 1 ), QStringList() << QStringLiteral( "symbol3D a" ) );
res = style.addSmartgroup( QStringLiteral( "tag" ), QStringLiteral( "OR" ), QStringList(), QStringList(), QStringList() << "c", QStringList() << "a" );
QCOMPARE( res, 2 );
@ -1158,6 +1337,7 @@ void TestStyle::testSmartGroup()
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::TextFormatEntity, 2 ), QStringList() << QStringLiteral( "different text bbb" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::LabelSettingsEntity, 2 ), QStringList() << QStringLiteral( "different l bbb" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::LegendPatchShapeEntity, 2 ), QStringList() << QStringLiteral( "different shp bbb" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::Symbol3DEntity, 2 ), QStringList() << QStringLiteral( "different symbol3D bbb" ) );
// tag some symbols
style.tagSymbol( QgsStyle::SymbolEntity, "symbolA", QStringList() << "red" << "blue" );
@ -1170,6 +1350,8 @@ void TestStyle::testSmartGroup()
style.tagSymbol( QgsStyle::LabelSettingsEntity, "different l bbb", QStringList() << "blue" << "red" );
style.tagSymbol( QgsStyle::LegendPatchShapeEntity, "shp a", QStringList() << "blue" );
style.tagSymbol( QgsStyle::LegendPatchShapeEntity, "different shp bbb", QStringList() << "blue" << "red" );
style.tagSymbol( QgsStyle::Symbol3DEntity, "symbol3D a", QStringList() << "blue" );
style.tagSymbol( QgsStyle::Symbol3DEntity, "different symbol3D bbb", QStringList() << "blue" << "red" );
// adding tags modifies groups!
QCOMPARE( groupModifiedSpy.count(), 4 );
@ -1187,6 +1369,7 @@ void TestStyle::testSmartGroup()
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::TextFormatEntity, 3 ), QStringList() << QStringLiteral( "format a" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::LabelSettingsEntity, 3 ), QStringList() << QStringLiteral( "settings a" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::LegendPatchShapeEntity, 3 ), QStringList() << QStringLiteral( "shp a" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::Symbol3DEntity, 3 ), QStringList() << QStringLiteral( "symbol3D a" ) );
res = style.addSmartgroup( QStringLiteral( "combined" ), QStringLiteral( "AND" ), QStringList() << "blue", QStringList(), QStringList(), QStringList() << "a" );
QCOMPARE( res, 4 );
@ -1201,6 +1384,7 @@ void TestStyle::testSmartGroup()
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::TextFormatEntity, 4 ), QStringList() << QStringLiteral( "different text bbb" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::LabelSettingsEntity, 4 ), QStringList() << QStringLiteral( "different l bbb" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::LegendPatchShapeEntity, 4 ), QStringList() << QStringLiteral( "different shp bbb" ) );
QCOMPARE( style.symbolsOfSmartgroup( QgsStyle::Symbol3DEntity, 4 ), QStringList() << QStringLiteral( "different symbol3D bbb" ) );
style.remove( QgsStyle::SmartgroupEntity, 1 );
QCOMPARE( style.smartgroupNames(), QStringList() << QStringLiteral( "tag" ) << QStringLiteral( "tags" ) << QStringLiteral( "combined" ) );
@ -1265,6 +1449,10 @@ class TestVisitor : public QgsStyleEntityVisitorInterface
mFound << QStringLiteral( "patch: %1 %2 %3" ).arg( entity.description, entity.identifier, static_cast< const QgsStyleLegendPatchShapeEntity * >( entity.entity )->shape().geometry().asWkt() );
break;
case QgsStyle::Symbol3DEntity:
mFound << QStringLiteral( "symbol 3d: %1 %2 %3" ).arg( entity.description, entity.identifier, static_cast< const QgsStyleSymbol3DEntity * >( entity.entity )->symbol()->type() );
break;
case QgsStyle::TagEntity:
case QgsStyle::SmartgroupEntity:
break;