Skip to content

Commit c5ab589

Browse files
committedSep 12, 2018
Add a QAbstractItemModel for showing the entities within a QgsStyle object
- also adds QgsStyleProxyModel which handles filtering of entities - lots of unit tests - new signals in QgsStyle for when symbols/tags/etc change (with tests)
1 parent cb0b335 commit c5ab589

File tree

12 files changed

+2180
-82
lines changed

12 files changed

+2180
-82
lines changed
 

‎python/core/auto_generated/symbology/qgsstyle.sip.in

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,13 @@ Constructor for QgsStyle.
3838
%End
3939
~QgsStyle();
4040

41-
enum StyleEntity { SymbolEntity, TagEntity, ColorrampEntity, SmartgroupEntity };
41+
enum StyleEntity
42+
{
43+
SymbolEntity,
44+
TagEntity,
45+
ColorrampEntity,
46+
SmartgroupEntity
47+
};
4248

4349
bool addSymbol( const QString &name, QgsSymbol *symbol /Transfer/, bool update = false );
4450
%Docstring
@@ -180,7 +186,9 @@ Removes symbol from style (and delete it)
180186

181187
bool renameSymbol( const QString &oldName, const QString &newName );
182188
%Docstring
183-
Changessymbol's name
189+
Renames a symbol from ``oldName`` to ``newName``.
190+
191+
Returns true if symbol was successfully renamed.
184192
%End
185193

186194
QgsSymbol *symbol( const QString &name ) /Factory/;
@@ -256,7 +264,7 @@ Removes the specified symbol from favorites
256264
:return: returns the success state as bool
257265
%End
258266

259-
void rename( StyleEntity type, int id, const QString &newName );
267+
bool rename( StyleEntity type, int id, const QString &newName );
260268
%Docstring
261269
Renames the given entity with the specified id
262270

@@ -265,7 +273,7 @@ Renames the given entity with the specified id
265273
:param newName: is the new name of the entity
266274
%End
267275

268-
void remove( StyleEntity type, int id );
276+
bool remove( StyleEntity type, int id );
269277
%Docstring
270278
Removes the specified entity from the db
271279

@@ -447,13 +455,89 @@ Imports the symbols and colorramps into the default style database from the give
447455
%End
448456

449457
signals:
458+
450459
void symbolSaved( const QString &name, QgsSymbol *symbol );
451460
%Docstring
452-
Is emitted every time a new symbol has been added to the database
461+
Emitted every time a new symbol has been added to the database.
462+
Emitted whenever a symbol has been added to the style and the database
463+
has been updated as a result.
464+
465+
.. seealso:: :py:func:`symbolRemoved`
466+
467+
.. seealso:: :py:func:`rampAdded`
453468
%End
469+
454470
void groupsModified();
455471
%Docstring
456472
Is emitted every time a tag or smartgroup has been added, removed, or renamed
473+
%End
474+
475+
void entityTagsChanged( QgsStyle::StyleEntity entity, const QString &name, const QStringList &newTags );
476+
%Docstring
477+
Emitted whenever an ``entity``'s tags are changed.
478+
479+
.. versionadded:: 3.4
480+
%End
481+
482+
void favoritedChanged( QgsStyle::StyleEntity entity, const QString &name, bool isFavorite );
483+
%Docstring
484+
Emitted whenever an ``entity`` is either favorited or un-favorited.
485+
486+
.. versionadded:: 3.4
487+
%End
488+
489+
void symbolRemoved( const QString &name );
490+
%Docstring
491+
Emitted whenever a symbol has been removed from the style and the database
492+
has been updated as a result.
493+
494+
.. seealso:: :py:func:`symbolSaved`
495+
496+
.. seealso:: :py:func:`rampRemoved`
497+
498+
.. versionadded:: 3.4
499+
%End
500+
501+
void symbolRenamed( const QString &oldName, const QString &newName );
502+
%Docstring
503+
Emitted whenever a symbol has been renamed from ``oldName`` to ``newName``
504+
505+
.. seealso:: :py:func:`rampRenamed`
506+
507+
.. versionadded:: 3.4
508+
%End
509+
510+
void rampRenamed( const QString &oldName, const QString &newName );
511+
%Docstring
512+
Emitted whenever a color ramp has been renamed from ``oldName`` to ``newName``
513+
514+
.. seealso:: :py:func:`symbolRenamed`
515+
516+
.. versionadded:: 3.4
517+
%End
518+
519+
void rampAdded( const QString &name );
520+
%Docstring
521+
Emitted whenever a color ramp has been added to the style and the database
522+
has been updated as a result.
523+
524+
.. seealso:: :py:func:`rampRemoved`
525+
526+
.. seealso:: :py:func:`symbolSaved`
527+
528+
.. versionadded:: 3.4
529+
%End
530+
531+
void rampRemoved( const QString &name );
532+
%Docstring
533+
Emitted whenever a color ramp has been removed from the style and the database
534+
has been updated as a result.
535+
536+
.. seealso:: :py:func:`rampAdded`
537+
538+
.. seealso:: :py:func:`symbolRemoved`
539+
540+
.. versionadded:: 3.4
457541
%End
458542

459543
};
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/core/symbology/qgsstylemodel.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
12+
class QgsStyleModel: QAbstractItemModel
13+
{
14+
%Docstring
15+
16+
A QAbstractItemModel subclass for showing symbol and color ramp entities contained
17+
within a QgsStyle database.
18+
19+
.. seealso:: :py:class:`QgsStyleProxyModel`
20+
21+
.. versionadded:: 3.4
22+
%End
23+
24+
%TypeHeaderCode
25+
#include "qgsstylemodel.h"
26+
%End
27+
public:
28+
29+
enum Column
30+
{
31+
Name,
32+
Tags,
33+
};
34+
35+
enum Role
36+
{
37+
TypeRole,
38+
TagRole,
39+
SymbolTypeRole,
40+
};
41+
42+
explicit QgsStyleModel( QgsStyle *style, QObject *parent /TransferThis/ = 0 );
43+
%Docstring
44+
Constructor for QgsStyleModel, for the specified ``style`` and ``parent`` object.
45+
46+
The ``style`` object must exist for the lifetime of this model.
47+
%End
48+
49+
virtual QVariant data( const QModelIndex &index, int role ) const;
50+
51+
virtual bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole );
52+
53+
virtual Qt::ItemFlags flags( const QModelIndex &index ) const;
54+
55+
virtual QVariant headerData( int section, Qt::Orientation orientation,
56+
int role = Qt::DisplayRole ) const;
57+
virtual QModelIndex index( int row, int column,
58+
const QModelIndex &parent = QModelIndex() ) const;
59+
virtual QModelIndex parent( const QModelIndex &index ) const;
60+
61+
virtual int rowCount( const QModelIndex &parent = QModelIndex() ) const;
62+
63+
virtual int columnCount( const QModelIndex &parent = QModelIndex() ) const;
64+
65+
66+
};
67+
68+
class QgsStyleProxyModel: QSortFilterProxyModel
69+
{
70+
%Docstring
71+
72+
A QSortFilterProxyModel subclass for showing filtered symbol and color ramps entries from a QgsStyle database.
73+
74+
.. seealso:: :py:class:`QgsStyleModel`
75+
76+
.. versionadded:: 3.4
77+
%End
78+
79+
%TypeHeaderCode
80+
#include "qgsstylemodel.h"
81+
%End
82+
public:
83+
84+
explicit QgsStyleProxyModel( QgsStyle *style, QObject *parent /TransferThis/ = 0 );
85+
%Docstring
86+
Constructor for QgsStyleProxyModel, for the specified ``style`` and ``parent`` object.
87+
88+
The ``style`` object must exist for the lifetime of this model.
89+
%End
90+
91+
QString filterString() const;
92+
%Docstring
93+
Returns the current filter string, if set.
94+
95+
.. seealso:: :py:func:`setFilterString`
96+
%End
97+
98+
QgsStyle::StyleEntity entityFilter() const;
99+
%Docstring
100+
Returns the style entity type filter.
101+
102+
.. note::
103+
104+
This filter is only active if entityFilterEnabled() is true.
105+
106+
.. seealso:: :py:func:`setEntityFilter`
107+
%End
108+
109+
void setEntityFilter( QgsStyle::StyleEntity filter );
110+
%Docstring
111+
Sets the style entity type ``filter``.
112+
113+
.. note::
114+
115+
This filter is only active if entityFilterEnabled() is true.
116+
117+
.. seealso:: :py:func:`entityFilter`
118+
%End
119+
120+
bool entityFilterEnabled() const;
121+
%Docstring
122+
Returns true if filtering by entity type is enabled.
123+
124+
.. seealso:: :py:func:`setEntityFilterEnabled`
125+
126+
.. seealso:: :py:func:`entityFilter`
127+
%End
128+
129+
void setEntityFilterEnabled( bool enabled );
130+
%Docstring
131+
Sets whether filtering by entity type is ``enabled``.
132+
133+
If ``enabled`` is false, then the value of entityFilter() will have no
134+
effect on the model filtering.
135+
136+
.. seealso:: :py:func:`entityFilterEnabled`
137+
138+
.. seealso:: :py:func:`setEntityFilter`
139+
%End
140+
141+
QgsSymbol::SymbolType symbolType() const;
142+
%Docstring
143+
Returns the symbol type filter.
144+
145+
.. note::
146+
147+
This filter is only active if symbolTypeFilterEnabled() is true, and has
148+
no effect on non-symbol entities (i.e. color ramps).
149+
150+
.. seealso:: :py:func:`setSymbolType`
151+
%End
152+
153+
void setSymbolType( QgsSymbol::SymbolType type );
154+
%Docstring
155+
Sets the symbol ``type`` filter.
156+
157+
.. note::
158+
159+
This filter is only active if symbolTypeFilterEnabled() is true.
160+
161+
.. seealso:: :py:func:`symbolType`
162+
%End
163+
164+
bool symbolTypeFilterEnabled() const;
165+
%Docstring
166+
Returns true if filtering by symbol type is enabled.
167+
168+
.. seealso:: :py:func:`setSymbolTypeFilterEnabled`
169+
170+
.. seealso:: :py:func:`symbolType`
171+
%End
172+
173+
void setSymbolTypeFilterEnabled( bool enabled );
174+
%Docstring
175+
Sets whether filtering by symbol type is ``enabled``.
176+
177+
If ``enabled`` is false, then the value of symbolType() will have no
178+
effect on the model filtering. This has
179+
no effect on non-symbol entities (i.e. color ramps).
180+
181+
.. seealso:: :py:func:`symbolTypeFilterEnabled`
182+
183+
.. seealso:: :py:func:`setSymbolType`
184+
%End
185+
186+
void setTagId( int id );
187+
%Docstring
188+
Sets a tag ``id`` to filter style entities by. Only entities with the given
189+
tag will be shown in the model.
190+
191+
Set ``id`` to -1 to disable tag filtering.
192+
193+
.. seealso:: :py:func:`tagId`
194+
%End
195+
196+
int tagId() const;
197+
%Docstring
198+
Returns the tag id used to filter style entities by.
199+
200+
If returned value is -1, then no tag filtering is being conducted.
201+
202+
.. seealso:: :py:func:`setTagId`
203+
%End
204+
205+
void setSmartGroupId( int id );
206+
%Docstring
207+
Sets a smart group ``id`` to filter style entities by. Only entities within the given
208+
smart group will be shown in the model.
209+
210+
Set ``id`` to -1 to disable smart group filtering.
211+
212+
.. seealso:: :py:func:`smartGroupId`
213+
%End
214+
215+
int smartGroupId() const;
216+
%Docstring
217+
Returns the smart group id used to filter style entities by.
218+
219+
If returned value is -1, then no smart group filtering is being conducted.
220+
221+
.. seealso:: :py:func:`setSmartGroupId`
222+
%End
223+
224+
virtual bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const;
225+
226+
227+
bool favoritesOnly() const;
228+
%Docstring
229+
Returns true if the model is showing only favorited entities.
230+
231+
.. seealso:: :py:func:`setFavoritesOnly`
232+
%End
233+
234+
void setFavoritesOnly( bool favoritesOnly );
235+
%Docstring
236+
Sets whether the model should show only favorited entities.
237+
238+
.. seealso:: :py:func:`setFavoritesOnly`
239+
%End
240+
241+
public slots:
242+
243+
void setFilterString( const QString &filter );
244+
%Docstring
245+
Sets a ``filter`` string, such that only symbol entities with names matching the
246+
specified string will be shown.
247+
248+
.. seealso:: :py:func:`filterString`
249+
%End
250+
251+
};
252+
253+
/************************************************************************
254+
* This file has been generated automatically from *
255+
* *
256+
* src/core/symbology/qgsstylemodel.h *
257+
* *
258+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
259+
************************************************************************/

‎python/core/core_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@
439439
%Include auto_generated/symbology/qgscptcityarchive.sip
440440
%Include auto_generated/symbology/qgssvgcache.sip
441441
%Include auto_generated/symbology/qgsstyle.sip
442+
%Include auto_generated/symbology/qgsstylemodel.sip
442443
%Include auto_generated/layertree/qgslayertree.sip
443444
%Include auto_generated/layertree/qgslayertreegroup.sip
444445
%Include auto_generated/layertree/qgslayertreelayer.sip

‎src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ SET(QGIS_CORE_SRCS
4747
symbology/qgsrulebasedrenderer.cpp
4848
symbology/qgssinglesymbolrenderer.cpp
4949
symbology/qgsstyle.cpp
50+
symbology/qgsstylemodel.cpp
5051
symbology/qgssvgcache.cpp
5152
symbology/qgssymbollayer.cpp
5253
symbology/qgssymbollayerregistry.cpp
@@ -746,6 +747,7 @@ SET(QGIS_CORE_MOC_HDRS
746747
symbology/qgscptcityarchive.h
747748
symbology/qgssvgcache.h
748749
symbology/qgsstyle.h
750+
symbology/qgsstylemodel.h
749751

750752
layertree/qgslayertree.h
751753
layertree/qgslayertreegroup.h

‎src/core/qgsapplication.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ void QgsApplication::init( QString profileFolder )
169169
qRegisterMetaType<QgsReferencedRectangle>( "QgsReferencedRectangle" );
170170
qRegisterMetaType<QgsReferencedPointXY>( "QgsReferencedPointXY" );
171171
qRegisterMetaType<QgsLayoutRenderContext::Flags>( "QgsLayoutRenderContext::Flags" );
172+
qRegisterMetaType<QgsStyle::StyleEntity>( "QgsStyle::StyleEntity" );
172173

173174
QString prefixPath( getenv( "QGIS_PREFIX_PATH" ) ? getenv( "QGIS_PREFIX_PATH" ) : applicationDirPath() );
174175
// QgsDebugMsg( QString( "prefixPath(): %1" ).arg( prefixPath ) );

‎src/core/symbology/qgsstyle.cpp

Lines changed: 100 additions & 72 deletions
Large diffs are not rendered by default.

‎src/core/symbology/qgsstyle.h

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ class CORE_EXPORT QgsStyle : public QObject
9292
* database functions are being run.
9393
* \sa rename(), remove(), symbolsOfFavorite(), symbolsWithTag(), symbolsOfSmartgroup()
9494
*/
95-
enum StyleEntity { SymbolEntity, TagEntity, ColorrampEntity, SmartgroupEntity };
95+
enum StyleEntity
96+
{
97+
SymbolEntity,
98+
TagEntity,
99+
ColorrampEntity,
100+
SmartgroupEntity
101+
};
96102

97103
/**
98104
* Adds a symbol to style and takes symbol's ownership
@@ -205,7 +211,11 @@ class CORE_EXPORT QgsStyle : public QObject
205211
//! Removes symbol from style (and delete it)
206212
bool removeSymbol( const QString &name );
207213

208-
//! Changessymbol's name
214+
/**
215+
* Renames a symbol from \a oldName to \a newName.
216+
*
217+
* Returns true if symbol was successfully renamed.
218+
*/
209219
bool renameSymbol( const QString &oldName, const QString &newName );
210220

211221
//! Returns a NEW copy of symbol
@@ -272,15 +282,15 @@ class CORE_EXPORT QgsStyle : public QObject
272282
* \param id is the DB id of the entity which is to be renamed
273283
* \param newName is the new name of the entity
274284
*/
275-
void rename( StyleEntity type, int id, const QString &newName );
285+
bool rename( StyleEntity type, int id, const QString &newName );
276286

277287
/**
278288
* Removes the specified entity from the db
279289
*
280290
* \param type is any of the style entities. Refer enum StyleEntity.
281291
* \param id is the DB id of the entity to be removed
282292
*/
283-
void remove( StyleEntity type, int id );
293+
bool remove( StyleEntity type, int id );
284294

285295
/**
286296
* Adds the symbol to the DB with the tags
@@ -415,11 +425,74 @@ class CORE_EXPORT QgsStyle : public QObject
415425
bool importXml( const QString &filename );
416426

417427
signals:
418-
//! Is emitted every time a new symbol has been added to the database
428+
429+
/**
430+
* Emitted every time a new symbol has been added to the database.
431+
* Emitted whenever a symbol has been added to the style and the database
432+
* has been updated as a result.
433+
* \see symbolRemoved()
434+
* \see rampAdded()
435+
*/
419436
void symbolSaved( const QString &name, QgsSymbol *symbol );
437+
420438
//! Is emitted every time a tag or smartgroup has been added, removed, or renamed
421439
void groupsModified();
422440

441+
/**
442+
* Emitted whenever an \a entity's tags are changed.
443+
*
444+
* \since QGIS 3.4
445+
*/
446+
void entityTagsChanged( QgsStyle::StyleEntity entity, const QString &name, const QStringList &newTags );
447+
448+
/**
449+
* Emitted whenever an \a entity is either favorited or un-favorited.
450+
*
451+
* \since QGIS 3.4
452+
*/
453+
void favoritedChanged( QgsStyle::StyleEntity entity, const QString &name, bool isFavorite );
454+
455+
/**
456+
* Emitted whenever a symbol has been removed from the style and the database
457+
* has been updated as a result.
458+
* \see symbolSaved()
459+
* \see rampRemoved()
460+
* \since QGIS 3.4
461+
*/
462+
void symbolRemoved( const QString &name );
463+
464+
/**
465+
* Emitted whenever a symbol has been renamed from \a oldName to \a newName
466+
* \see rampRenamed()
467+
* \since QGIS 3.4
468+
*/
469+
void symbolRenamed( const QString &oldName, const QString &newName );
470+
471+
/**
472+
* Emitted whenever a color ramp has been renamed from \a oldName to \a newName
473+
* \see symbolRenamed()
474+
* \since QGIS 3.4
475+
*/
476+
void rampRenamed( const QString &oldName, const QString &newName );
477+
478+
/**
479+
* Emitted whenever a color ramp has been added to the style and the database
480+
* has been updated as a result.
481+
* \see rampRemoved()
482+
* \see symbolSaved()
483+
* \since QGIS 3.4
484+
*/
485+
void rampAdded( const QString &name );
486+
487+
/**
488+
* Emitted whenever a color ramp has been removed from the style and the database
489+
* has been updated as a result.
490+
* \see rampAdded()
491+
* \see symbolRemoved()
492+
* \since QGIS 3.4
493+
*/
494+
void rampRemoved( const QString &name );
495+
423496
private:
424497

425498
QgsSymbolMap mSymbols;

‎src/core/symbology/qgsstylemodel.cpp

Lines changed: 529 additions & 0 deletions
Large diffs are not rendered by default.

‎src/core/symbology/qgsstylemodel.h

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/***************************************************************************
2+
qgsstylemodel.h
3+
---------------
4+
begin : September 2018
5+
copyright : (C) 2018 by Nyall Dawson
6+
email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#ifndef QGSSTYLEMODEL_H
17+
#define QGSSTYLEMODEL_H
18+
19+
#include "qgis_core.h"
20+
#include "qgis_sip.h"
21+
#include "qgsstyle.h"
22+
#include "qgssymbol.h"
23+
#include <QAbstractItemModel>
24+
#include <QSortFilterProxyModel>
25+
26+
class QgsSymbol;
27+
28+
/**
29+
* \ingroup core
30+
* \class QgsStyleModel
31+
*
32+
* A QAbstractItemModel subclass for showing symbol and color ramp entities contained
33+
* within a QgsStyle database.
34+
*
35+
* \see QgsStyleProxyModel
36+
*
37+
* \since QGIS 3.4
38+
*/
39+
class CORE_EXPORT QgsStyleModel: public QAbstractItemModel
40+
{
41+
Q_OBJECT
42+
43+
public:
44+
45+
//! Model columns
46+
enum Column
47+
{
48+
Name = 0, //!< Name column
49+
Tags, //!< Tags column
50+
};
51+
52+
//! Custom model roles
53+
enum Role
54+
{
55+
TypeRole = Qt::UserRole + 1, //!< Style entity type, see QgsStyle::StyleEntity
56+
TagRole, //!< String list of tags
57+
SymbolTypeRole, //!< Symbol type (for symbol entities)
58+
};
59+
60+
/**
61+
* Constructor for QgsStyleModel, for the specified \a style and \a parent object.
62+
*
63+
* The \a style object must exist for the lifetime of this model.
64+
*/
65+
explicit QgsStyleModel( QgsStyle *style, QObject *parent SIP_TRANSFERTHIS = nullptr );
66+
67+
QVariant data( const QModelIndex &index, int role ) const override;
68+
bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override;
69+
Qt::ItemFlags flags( const QModelIndex &index ) const override;
70+
QVariant headerData( int section, Qt::Orientation orientation,
71+
int role = Qt::DisplayRole ) const override;
72+
QModelIndex index( int row, int column,
73+
const QModelIndex &parent = QModelIndex() ) const override;
74+
QModelIndex parent( const QModelIndex &index ) const override;
75+
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
76+
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
77+
78+
private slots:
79+
80+
void onSymbolAdded( const QString &name, QgsSymbol *symbol );
81+
void onSymbolRemoved( const QString &name );
82+
void onSymbolRename( const QString &oldName, const QString &newName );
83+
void onRampAdded( const QString &name );
84+
void onRampRemoved( const QString &name );
85+
void onRampRename( const QString &oldName, const QString &newName );
86+
void onTagsChanged( int entity, const QString &name, const QStringList &tags );
87+
88+
private:
89+
90+
QgsStyle *mStyle = nullptr;
91+
QStringList mSymbolNames;
92+
QStringList mRampNames;
93+
94+
};
95+
96+
/**
97+
* \ingroup core
98+
* \class QgsStyleProxyModel
99+
*
100+
* A QSortFilterProxyModel subclass for showing filtered symbol and color ramps entries from a QgsStyle database.
101+
*
102+
* \see QgsStyleModel
103+
*
104+
* \since QGIS 3.4
105+
*/
106+
class CORE_EXPORT QgsStyleProxyModel: public QSortFilterProxyModel
107+
{
108+
Q_OBJECT
109+
110+
public:
111+
112+
/**
113+
* Constructor for QgsStyleProxyModel, for the specified \a style and \a parent object.
114+
*
115+
* The \a style object must exist for the lifetime of this model.
116+
*/
117+
explicit QgsStyleProxyModel( QgsStyle *style, QObject *parent SIP_TRANSFERTHIS = nullptr );
118+
119+
/**
120+
* Returns the current filter string, if set.
121+
*
122+
* \see setFilterString()
123+
*/
124+
QString filterString() const { return mFilterString; }
125+
126+
/**
127+
* Returns the style entity type filter.
128+
*
129+
* \note This filter is only active if entityFilterEnabled() is true.
130+
* \see setEntityFilter()
131+
*/
132+
QgsStyle::StyleEntity entityFilter() const;
133+
134+
/**
135+
* Sets the style entity type \a filter.
136+
*
137+
* \note This filter is only active if entityFilterEnabled() is true.
138+
*
139+
* \see entityFilter()
140+
*/
141+
void setEntityFilter( QgsStyle::StyleEntity filter );
142+
143+
/**
144+
* Returns true if filtering by entity type is enabled.
145+
*
146+
* \see setEntityFilterEnabled()
147+
* \see entityFilter()
148+
*/
149+
bool entityFilterEnabled() const;
150+
151+
/**
152+
* Sets whether filtering by entity type is \a enabled.
153+
*
154+
* If \a enabled is false, then the value of entityFilter() will have no
155+
* effect on the model filtering.
156+
*
157+
* \see entityFilterEnabled()
158+
* \see setEntityFilter()
159+
*/
160+
void setEntityFilterEnabled( bool enabled );
161+
162+
/**
163+
* Returns the symbol type filter.
164+
*
165+
* \note This filter is only active if symbolTypeFilterEnabled() is true, and has
166+
* no effect on non-symbol entities (i.e. color ramps).
167+
*
168+
* \see setSymbolType()
169+
*/
170+
QgsSymbol::SymbolType symbolType() const;
171+
172+
/**
173+
* Sets the symbol \a type filter.
174+
*
175+
* \note This filter is only active if symbolTypeFilterEnabled() is true.
176+
*
177+
* \see symbolType()
178+
*/
179+
void setSymbolType( QgsSymbol::SymbolType type );
180+
181+
/**
182+
* Returns true if filtering by symbol type is enabled.
183+
*
184+
* \see setSymbolTypeFilterEnabled()
185+
* \see symbolType()
186+
*/
187+
bool symbolTypeFilterEnabled() const;
188+
189+
/**
190+
* Sets whether filtering by symbol type is \a enabled.
191+
*
192+
* If \a enabled is false, then the value of symbolType() will have no
193+
* effect on the model filtering. This has
194+
* no effect on non-symbol entities (i.e. color ramps).
195+
*
196+
* \see symbolTypeFilterEnabled()
197+
* \see setSymbolType()
198+
*/
199+
void setSymbolTypeFilterEnabled( bool enabled );
200+
201+
/**
202+
* Sets a tag \a id to filter style entities by. Only entities with the given
203+
* tag will be shown in the model.
204+
*
205+
* Set \a id to -1 to disable tag filtering.
206+
*
207+
* \see tagId()
208+
*/
209+
void setTagId( int id );
210+
211+
/**
212+
* Returns the tag id used to filter style entities by.
213+
*
214+
* If returned value is -1, then no tag filtering is being conducted.
215+
*
216+
* \see setTagId()
217+
*/
218+
int tagId() const;
219+
220+
/**
221+
* Sets a smart group \a id to filter style entities by. Only entities within the given
222+
* smart group will be shown in the model.
223+
*
224+
* Set \a id to -1 to disable smart group filtering.
225+
*
226+
* \see smartGroupId()
227+
*/
228+
void setSmartGroupId( int id );
229+
230+
/**
231+
* Returns the smart group id used to filter style entities by.
232+
*
233+
* If returned value is -1, then no smart group filtering is being conducted.
234+
*
235+
* \see setSmartGroupId()
236+
*/
237+
int smartGroupId() const;
238+
239+
bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override;
240+
241+
/**
242+
* Returns true if the model is showing only favorited entities.
243+
*
244+
* \see setFavoritesOnly()
245+
*/
246+
bool favoritesOnly() const;
247+
248+
/**
249+
* Sets whether the model should show only favorited entities.
250+
*
251+
* \see setFavoritesOnly()
252+
*/
253+
void setFavoritesOnly( bool favoritesOnly );
254+
255+
public slots:
256+
257+
/**
258+
* Sets a \a filter string, such that only symbol entities with names matching the
259+
* specified string will be shown.
260+
*
261+
* \see filterString()
262+
*/
263+
void setFilterString( const QString &filter );
264+
265+
private:
266+
267+
QgsStyleModel *mModel = nullptr;
268+
QgsStyle *mStyle = nullptr;
269+
270+
QString mFilterString;
271+
272+
int mTagId = -1;
273+
QStringList mTaggedSymbolNames;
274+
275+
int mSmartGroupId = -1;
276+
QStringList mSmartGroupSymbolNames;
277+
278+
bool mFavoritesOnly = false;
279+
QStringList mFavoritedSymbolNames;
280+
281+
bool mEntityFilterEnabled = false;
282+
QgsStyle::StyleEntity mEntityFilter = QgsStyle::SymbolEntity;
283+
284+
bool mSymbolTypeFilterEnabled = false;
285+
QgsSymbol::SymbolType mSymbolType = QgsSymbol::Marker;
286+
287+
};
288+
289+
#endif //QGSSTYLEMODEL_H

‎tests/src/core/testqgsstyle.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,20 +265,58 @@ void TestStyle::testFavorites()
265265
QVERIFY( favorites.contains( "symbolA" ) );
266266
QVERIFY( favorites.contains( "symbolC" ) );
267267

268+
QSignalSpy favoriteChangedSpy( mStyle, &QgsStyle::favoritedChanged );
269+
268270
// remove one symbol from favorites
269271
mStyle->removeFavorite( QgsStyle::SymbolEntity, QStringLiteral( "symbolA" ) );
272+
QCOMPARE( favoriteChangedSpy.count(), 1 );
273+
QCOMPARE( favoriteChangedSpy.at( 0 ).at( 0 ).toInt(), QgsStyle::SymbolEntity );
274+
QCOMPARE( favoriteChangedSpy.at( 0 ).at( 1 ).toString(), QStringLiteral( "symbolA" ) );
275+
QCOMPARE( favoriteChangedSpy.at( 0 ).at( 2 ).toBool(), false );
270276

271277
// insure favorites updated after removal
272278
favorites = mStyle->symbolsOfFavorite( QgsStyle::SymbolEntity );
273279
QCOMPARE( favorites.count(), count + 1 );
274280
QVERIFY( favorites.contains( "symbolC" ) );
281+
282+
mStyle->addFavorite( QgsStyle::SymbolEntity, QStringLiteral( "symbolA" ) );
283+
QCOMPARE( favoriteChangedSpy.count(), 2 );
284+
QCOMPARE( favoriteChangedSpy.at( 1 ).at( 0 ).toInt(), QgsStyle::SymbolEntity );
285+
QCOMPARE( favoriteChangedSpy.at( 1 ).at( 1 ).toString(), QStringLiteral( "symbolA" ) );
286+
QCOMPARE( favoriteChangedSpy.at( 1 ).at( 2 ).toBool(), true );
287+
favorites = mStyle->symbolsOfFavorite( QgsStyle::SymbolEntity );
288+
QCOMPARE( favorites.count(), count + 2 );
289+
QVERIFY( favorites.contains( "symbolA" ) );
290+
291+
QgsGradientColorRamp *gradientRamp = new QgsGradientColorRamp( QColor( Qt::red ), QColor( Qt::blue ) );
292+
QVERIFY( mStyle->addColorRamp( "gradient_1", gradientRamp, true ) );
293+
favorites = mStyle->symbolsOfFavorite( QgsStyle::ColorrampEntity );
294+
QCOMPARE( favorites.count(), 0 );
295+
296+
mStyle->addFavorite( QgsStyle::ColorrampEntity, QStringLiteral( "gradient_1" ) );
297+
QCOMPARE( favoriteChangedSpy.count(), 3 );
298+
QCOMPARE( favoriteChangedSpy.at( 2 ).at( 0 ).toInt(), QgsStyle::ColorrampEntity );
299+
QCOMPARE( favoriteChangedSpy.at( 2 ).at( 1 ).toString(), QStringLiteral( "gradient_1" ) );
300+
QCOMPARE( favoriteChangedSpy.at( 2 ).at( 2 ).toBool(), true );
301+
favorites = mStyle->symbolsOfFavorite( QgsStyle::ColorrampEntity );
302+
QCOMPARE( favorites.count(), 1 );
303+
QVERIFY( favorites.contains( "gradient_1" ) );
304+
305+
mStyle->removeFavorite( QgsStyle::ColorrampEntity, QStringLiteral( "gradient_1" ) );
306+
QCOMPARE( favoriteChangedSpy.count(), 4 );
307+
QCOMPARE( favoriteChangedSpy.at( 3 ).at( 0 ).toInt(), QgsStyle::ColorrampEntity );
308+
QCOMPARE( favoriteChangedSpy.at( 3 ).at( 1 ).toString(), QStringLiteral( "gradient_1" ) );
309+
QCOMPARE( favoriteChangedSpy.at( 3 ).at( 2 ).toBool(), false );
310+
favorites = mStyle->symbolsOfFavorite( QgsStyle::ColorrampEntity );
311+
QCOMPARE( favorites.count(), 0 );
275312
}
276313

277314
void TestStyle::testTags()
278315
{
279316
//add some tags
280317
int id = mStyle->addTag( QStringLiteral( "red" ) );
281318
QCOMPARE( id, mStyle->tagId( "red" ) );
319+
282320
id = mStyle->addTag( QStringLiteral( "starry" ) );
283321
QCOMPARE( id, mStyle->tagId( "starry" ) );
284322
id = mStyle->addTag( QStringLiteral( "circle" ) );
@@ -321,13 +359,31 @@ void TestStyle::testTags()
321359
mStyle->addSymbol( QStringLiteral( "red circle" ), sym3.release(), true );
322360
mStyle->addSymbol( QStringLiteral( "МЕТЕОР" ), sym4.release(), true );
323361

362+
QSignalSpy tagsChangedSpy( mStyle, &QgsStyle::entityTagsChanged );
363+
324364
//tag them
325365
QVERIFY( mStyle->tagSymbol( QgsStyle::SymbolEntity, "blue starry", QStringList() << "blue" << "starry" ) );
366+
QCOMPARE( tagsChangedSpy.count(), 1 );
367+
QCOMPARE( tagsChangedSpy.at( 0 ).at( 0 ).toInt(), QgsStyle::SymbolEntity );
368+
QCOMPARE( tagsChangedSpy.at( 0 ).at( 1 ).toString(), QStringLiteral( "blue starry" ) );
369+
QCOMPARE( tagsChangedSpy.at( 0 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "blue" ) << QStringLiteral( "starry" ) );
370+
326371
QVERIFY( mStyle->tagSymbol( QgsStyle::SymbolEntity, "red circle", QStringList() << "red" << "circle" ) );
372+
QCOMPARE( tagsChangedSpy.count(), 2 );
373+
QCOMPARE( tagsChangedSpy.at( 1 ).at( 0 ).toInt(), QgsStyle::SymbolEntity );
374+
QCOMPARE( tagsChangedSpy.at( 1 ).at( 1 ).toString(), QStringLiteral( "red circle" ) );
375+
QCOMPARE( tagsChangedSpy.at( 1 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "red" ) << QStringLiteral( "circle" ) );
376+
327377
//bad symbol name
328378
QVERIFY( !mStyle->tagSymbol( QgsStyle::SymbolEntity, "no symbol", QStringList() << "red" << "circle" ) );
379+
QCOMPARE( tagsChangedSpy.count(), 2 );
329380
//tag which hasn't been added yet
330381
QVERIFY( mStyle->tagSymbol( QgsStyle::SymbolEntity, "red circle", QStringList() << "round" ) );
382+
QCOMPARE( tagsChangedSpy.count(), 3 );
383+
QCOMPARE( tagsChangedSpy.at( 2 ).at( 0 ).toInt(), QgsStyle::SymbolEntity );
384+
QCOMPARE( tagsChangedSpy.at( 2 ).at( 1 ).toString(), QStringLiteral( "red circle" ) );
385+
QCOMPARE( tagsChangedSpy.at( 2 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "red" ) << QStringLiteral( "circle" ) << QStringLiteral( "round" ) );
386+
331387
tags = mStyle->tags();
332388
QVERIFY( tags.contains( "round" ) );
333389

@@ -369,9 +425,25 @@ void TestStyle::testTags()
369425
tags = mStyle->tagsOfSymbol( QgsStyle::SymbolEntity, QStringLiteral( "blue starry" ) );
370426
QCOMPARE( tags.count(), 1 );
371427
QVERIFY( tags.contains( "starry" ) );
428+
QCOMPARE( tagsChangedSpy.count(), 5 );
429+
QCOMPARE( tagsChangedSpy.at( 4 ).at( 0 ).toInt(), QgsStyle::SymbolEntity );
430+
QCOMPARE( tagsChangedSpy.at( 4 ).at( 1 ).toString(), QStringLiteral( "blue starry" ) );
431+
QCOMPARE( tagsChangedSpy.at( 4 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "starry" ) );
432+
433+
// completely detag symbol
434+
QVERIFY( mStyle->detagSymbol( QgsStyle::SymbolEntity, QStringLiteral( "blue starry" ) ) );
435+
tags = mStyle->tagsOfSymbol( QgsStyle::SymbolEntity, QStringLiteral( "blue starry" ) );
436+
QCOMPARE( tags.count(), 0 );
437+
QCOMPARE( tagsChangedSpy.count(), 6 );
438+
QCOMPARE( tagsChangedSpy.at( 5 ).at( 0 ).toInt(), QgsStyle::SymbolEntity );
439+
QCOMPARE( tagsChangedSpy.at( 5 ).at( 1 ).toString(), QStringLiteral( "blue starry" ) );
440+
QCOMPARE( tagsChangedSpy.at( 5 ).at( 2 ).toStringList(), QStringList() );
372441

373442
//try to remove tag from non-existing symbol
374443
QVERIFY( !mStyle->detagSymbol( QgsStyle::SymbolEntity, "no symbol!", QStringList() << "bad" << "blue" ) );
444+
QCOMPARE( tagsChangedSpy.count(), 6 );
445+
446+
mStyle->tagSymbol( QgsStyle::SymbolEntity, "blue starry", QStringList() << QStringLiteral( "starry" ) );
375447

376448
//check symbols with tag
377449
QStringList symbols = mStyle->symbolsWithTag( QgsStyle::SymbolEntity, mStyle->tagId( QStringLiteral( "red" ) ) );
@@ -417,6 +489,66 @@ void TestStyle::testTags()
417489
QCOMPARE( symbols.count(), 1 );
418490
QVERIFY( symbols.contains( "МЕТЕОР" ) );
419491

492+
// tag ramp
493+
QgsGradientColorRamp *gradientRamp = new QgsGradientColorRamp( QColor( Qt::red ), QColor( Qt::blue ) );
494+
QVERIFY( mStyle->addColorRamp( "gradient_tag1", gradientRamp, true ) );
495+
QgsGradientColorRamp *gradientRamp2 = new QgsGradientColorRamp( QColor( Qt::red ), QColor( Qt::blue ) );
496+
QVERIFY( mStyle->addColorRamp( "gradient_tag2", gradientRamp2, true ) );
497+
498+
QVERIFY( mStyle->tagSymbol( QgsStyle::ColorrampEntity, "gradient_tag1", QStringList() << "blue" << "starry" ) );
499+
QCOMPARE( tagsChangedSpy.count(), 10 );
500+
QCOMPARE( tagsChangedSpy.at( 9 ).at( 0 ).toInt(), static_cast< int>( QgsStyle::ColorrampEntity ) );
501+
QCOMPARE( tagsChangedSpy.at( 9 ).at( 1 ).toString(), QStringLiteral( "gradient_tag1" ) );
502+
QCOMPARE( tagsChangedSpy.at( 9 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "blue" ) << QStringLiteral( "starry" ) );
503+
504+
QVERIFY( mStyle->tagSymbol( QgsStyle::ColorrampEntity, "gradient_tag2", QStringList() << "red" << "circle" ) );
505+
QCOMPARE( tagsChangedSpy.count(), 11 );
506+
QCOMPARE( tagsChangedSpy.at( 10 ).at( 0 ).toInt(), static_cast< int>( QgsStyle::ColorrampEntity ) );
507+
QCOMPARE( tagsChangedSpy.at( 10 ).at( 1 ).toString(), QStringLiteral( "gradient_tag2" ) );
508+
QCOMPARE( tagsChangedSpy.at( 10 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "red" ) << QStringLiteral( "circle" ) );
509+
510+
//bad ramp name
511+
QVERIFY( !mStyle->tagSymbol( QgsStyle::ColorrampEntity, "no ramp", QStringList() << "red" << "circle" ) );
512+
QCOMPARE( tagsChangedSpy.count(), 11 );
513+
//tag which hasn't been added yet
514+
QVERIFY( mStyle->tagSymbol( QgsStyle::ColorrampEntity, "gradient_tag2", QStringList() << "round ramp" ) );
515+
QCOMPARE( tagsChangedSpy.count(), 12 );
516+
QCOMPARE( tagsChangedSpy.at( 11 ).at( 0 ).toInt(), static_cast< int>( QgsStyle::ColorrampEntity ) );
517+
QCOMPARE( tagsChangedSpy.at( 11 ).at( 1 ).toString(), QStringLiteral( "gradient_tag2" ) );
518+
QCOMPARE( tagsChangedSpy.at( 11 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "red" ) << QStringLiteral( "circle" ) << QStringLiteral( "round ramp" ) );
519+
520+
tags = mStyle->tags();
521+
QVERIFY( tags.contains( QStringLiteral( "round ramp" ) ) );
522+
523+
//check that tags have been applied
524+
tags = mStyle->tagsOfSymbol( QgsStyle::ColorrampEntity, QStringLiteral( "gradient_tag1" ) );
525+
QCOMPARE( tags.count(), 2 );
526+
QVERIFY( tags.contains( "blue" ) );
527+
QVERIFY( tags.contains( "starry" ) );
528+
tags = mStyle->tagsOfSymbol( QgsStyle::ColorrampEntity, QStringLiteral( "gradient_tag2" ) );
529+
QCOMPARE( tags.count(), 3 );
530+
QVERIFY( tags.contains( "red" ) );
531+
QVERIFY( tags.contains( "circle" ) );
532+
QVERIFY( tags.contains( "round ramp" ) );
533+
534+
//remove a tag, including a non-present tag
535+
QVERIFY( mStyle->detagSymbol( QgsStyle::ColorrampEntity, "gradient_tag1", QStringList() << "bad" << "blue" ) );
536+
tags = mStyle->tagsOfSymbol( QgsStyle::ColorrampEntity, QStringLiteral( "gradient_tag1" ) );
537+
QCOMPARE( tags.count(), 1 );
538+
QVERIFY( tags.contains( "starry" ) );
539+
QCOMPARE( tagsChangedSpy.count(), 13 );
540+
QCOMPARE( tagsChangedSpy.at( 12 ).at( 0 ).toInt(), QgsStyle::ColorrampEntity );
541+
QCOMPARE( tagsChangedSpy.at( 12 ).at( 1 ).toString(), QStringLiteral( "gradient_tag1" ) );
542+
QCOMPARE( tagsChangedSpy.at( 12 ).at( 2 ).toStringList(), QStringList() << QStringLiteral( "starry" ) );
543+
544+
// completely detag symbol
545+
QVERIFY( mStyle->detagSymbol( QgsStyle::ColorrampEntity, QStringLiteral( "gradient_tag1" ) ) );
546+
tags = mStyle->tagsOfSymbol( QgsStyle::ColorrampEntity, QStringLiteral( "gradient_tag1" ) );
547+
QCOMPARE( tags.count(), 0 );
548+
QCOMPARE( tagsChangedSpy.count(), 14 );
549+
QCOMPARE( tagsChangedSpy.at( 13 ).at( 0 ).toInt(), QgsStyle::ColorrampEntity );
550+
QCOMPARE( tagsChangedSpy.at( 13 ).at( 1 ).toString(), QStringLiteral( "gradient_tag1" ) );
551+
QCOMPARE( tagsChangedSpy.at( 13 ).at( 2 ).toStringList(), QStringList() );
420552
}
421553

422554
QGSTEST_MAIN( TestStyle )

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ ADD_PYTHON_TEST(PyQgsArrowSymbolLayer test_qgsarrowsymbollayer.py)
184184
ADD_PYTHON_TEST(PyQgsSymbolExpressionVariables test_qgssymbolexpressionvariables.py)
185185
ADD_PYTHON_TEST(PyQgsSyntacticSugar test_syntactic_sugar.py)
186186
ADD_PYTHON_TEST(PyQgsStringUtils test_qgsstringutils.py)
187+
ADD_PYTHON_TEST(PyQgsStyleModel test_qgsstylemodel.py)
187188
ADD_PYTHON_TEST(PyQgsSvgSourceLineEdit test_qgssvgsourcelineedit.py)
188189
ADD_PYTHON_TEST(PyQgsSymbol test_qgssymbol.py)
189190
ADD_PYTHON_TEST(PyQgsSymbolLayerUtils test_qgssymbollayerutils.py)

‎tests/src/python/test_qgsstylemodel.py

Lines changed: 699 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.