Skip to content

Commit bc449c4

Browse files
committed
Move generation of default symbol random colors to
QgsColorSchemeRegistry and add API to set a QgsColorScheme from which to pull colors when creating a random symbol color.
1 parent 40ceb7b commit bc449c4

File tree

5 files changed

+246
-8
lines changed

5 files changed

+246
-8
lines changed

python/core/qgscolorschemeregistry.sip.in

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,63 @@ Returns all color schemes in the registry which have a specified flag set
104104

105105

106106

107+
void setRandomStyleColorScheme( QgsColorScheme *scheme );
108+
%Docstring
109+
Sets the color ``scheme`` to use when fetching random colors to use for symbol styles.
110+
111+
``scheme`` should match a color scheme which is already present in the registry.
112+
113+
Note that calling this method takes a snapshot of the colors from the scheme's
114+
QgsColorScheme.fetchColors() list. Accordingly, any future changes to the colors
115+
in ``scheme`` are not automatically reflected by calls to fetchRandomStyleColor().
116+
If ``scheme`` is updated, then another call to setRandomStyleColorScheme() must
117+
be made in order to update the cached list of available style colors.
118+
119+
.. seealso:: :py:func:`randomStyleColorScheme`
120+
121+
.. seealso:: :py:func:`fetchRandomStyleColor`
122+
123+
.. versionadded:: 3.2
124+
%End
125+
126+
QgsColorScheme *randomStyleColorScheme();
127+
%Docstring
128+
Returns the color scheme used when fetching random colors to use for symbol styles.
129+
130+
This may be None, in which case totally random colors are used for styles.
131+
132+
.. seealso:: :py:func:`setRandomStyleColorScheme`
133+
134+
.. seealso:: :py:func:`fetchRandomStyleColor`
135+
136+
.. versionadded:: 3.2
137+
%End
138+
139+
QColor fetchRandomStyleColor() const;
140+
%Docstring
141+
Returns a random color for use with a new symbol style (e.g. for a newly created
142+
map layer).
143+
144+
If a randomStyleColorScheme() is set then this color will be randomly taken from that
145+
color scheme. If no randomStyleColorScheme() is set then a totally random color
146+
will be generated.
147+
148+
Note that calling setRandomStyleColorScheme() takes a snapshot of the colors from the scheme's
149+
QgsColorScheme.fetchColors() list. Accordingly, any future changes to the colors
150+
in the scheme are not automatically reflected by calls to fetchRandomStyleColor().
151+
If the scheme is updated, then another call to setRandomStyleColorScheme() must
152+
be made in order to update the cached list of available style colors from which
153+
fetchRandomStyleColor() selects colors.
154+
155+
This method is thread safe.
156+
157+
.. seealso:: :py:func:`randomStyleColorScheme`
158+
159+
.. seealso:: :py:func:`setRandomStyleColorScheme`
160+
161+
.. versionadded:: 3.2
162+
%End
163+
107164
};
108165

109166

src/core/qgscolorschemeregistry.cpp

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
#include "qgsapplication.h"
2121
#include <QDir>
2222
#include <QFileInfoList>
23-
23+
#include <QMutex>
24+
#include <random>
2425

2526
QgsColorSchemeRegistry::~QgsColorSchemeRegistry()
2627
{
@@ -98,8 +99,67 @@ QList<QgsColorScheme *> QgsColorSchemeRegistry::schemes( const QgsColorScheme::S
9899
return matchingSchemes;
99100
}
100101

102+
void QgsColorSchemeRegistry::setRandomStyleColorScheme( QgsColorScheme *scheme )
103+
{
104+
mRandomStyleColorScheme = scheme;
105+
if ( scheme )
106+
{
107+
mRandomStyleColors = scheme->fetchColors();
108+
109+
std::random_device rd;
110+
std::mt19937 mt( rd() );
111+
std::uniform_int_distribution<int> colorDist( 0, mRandomStyleColors.count() - 1 );
112+
mNextRandomStyleColorIndex = colorDist( mt );
113+
std::uniform_int_distribution<int> colorDir( 0, 1 );
114+
mNextRandomStyleColorDirection = colorDir( mt ) == 0 ? -1 : 1;
115+
}
116+
else
117+
{
118+
mRandomStyleColors.clear();
119+
}
120+
}
121+
122+
QgsColorScheme *QgsColorSchemeRegistry::randomStyleColorScheme()
123+
{
124+
return mRandomStyleColorScheme;
125+
}
126+
127+
QColor QgsColorSchemeRegistry::fetchRandomStyleColor() const
128+
{
129+
if ( mRandomStyleColors.empty() )
130+
{
131+
// no random color scheme available - so just use totally random colors
132+
133+
// Make sure we use get uniquely seeded random numbers, and not the same sequence of numbers
134+
std::random_device rd;
135+
std::mt19937 mt( rd() );
136+
std::uniform_int_distribution<int> hueDist( 0, 359 );
137+
std::uniform_int_distribution<int> satDist( 64, 255 );
138+
std::uniform_int_distribution<int> valueDist( 128, 255 );
139+
return QColor::fromHsv( hueDist( mt ), satDist( mt ), valueDist( mt ) );
140+
}
141+
else
142+
{
143+
static QMutex sMutex;
144+
QMutexLocker locker( &sMutex );
145+
QColor res = mRandomStyleColors.at( mNextRandomStyleColorIndex ).first;
146+
mNextRandomStyleColorIndex += mNextRandomStyleColorDirection;
147+
if ( mNextRandomStyleColorIndex < 0 )
148+
mNextRandomStyleColorIndex = mRandomStyleColors.count() - 1;
149+
if ( mNextRandomStyleColorIndex >= mRandomStyleColors.count() )
150+
mNextRandomStyleColorIndex = 0;
151+
return res;
152+
}
153+
}
154+
101155
bool QgsColorSchemeRegistry::removeColorScheme( QgsColorScheme *scheme )
102156
{
157+
if ( mRandomStyleColorScheme == scheme )
158+
{
159+
mRandomStyleColorScheme = nullptr;
160+
mRandomStyleColors.clear();
161+
}
162+
103163
if ( mColorSchemeList.indexOf( scheme ) != -1 )
104164
{
105165
mColorSchemeList.removeAll( scheme );

src/core/qgscolorschemeregistry.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,70 @@ class CORE_EXPORT QgsColorSchemeRegistry
119119
}
120120
#endif
121121

122+
/**
123+
* Sets the color \a scheme to use when fetching random colors to use for symbol styles.
124+
*
125+
* \a scheme should match a color scheme which is already present in the registry.
126+
*
127+
* Note that calling this method takes a snapshot of the colors from the scheme's
128+
* QgsColorScheme::fetchColors() list. Accordingly, any future changes to the colors
129+
* in \a scheme are not automatically reflected by calls to fetchRandomStyleColor().
130+
* If \a scheme is updated, then another call to setRandomStyleColorScheme() must
131+
* be made in order to update the cached list of available style colors.
132+
*
133+
* \see randomStyleColorScheme()
134+
* \see fetchRandomStyleColor()
135+
*
136+
* \since QGIS 3.2
137+
*/
138+
void setRandomStyleColorScheme( QgsColorScheme *scheme );
139+
140+
/**
141+
* Returns the color scheme used when fetching random colors to use for symbol styles.
142+
*
143+
* This may be nullptr, in which case totally random colors are used for styles.
144+
*
145+
* \see setRandomStyleColorScheme()
146+
* \see fetchRandomStyleColor()
147+
*
148+
* \since QGIS 3.2
149+
*/
150+
QgsColorScheme *randomStyleColorScheme();
151+
152+
/**
153+
* Returns a random color for use with a new symbol style (e.g. for a newly created
154+
* map layer).
155+
*
156+
* If a randomStyleColorScheme() is set then this color will be randomly taken from that
157+
* color scheme. If no randomStyleColorScheme() is set then a totally random color
158+
* will be generated.
159+
*
160+
* Note that calling setRandomStyleColorScheme() takes a snapshot of the colors from the scheme's
161+
* QgsColorScheme::fetchColors() list. Accordingly, any future changes to the colors
162+
* in the scheme are not automatically reflected by calls to fetchRandomStyleColor().
163+
* If the scheme is updated, then another call to setRandomStyleColorScheme() must
164+
* be made in order to update the cached list of available style colors from which
165+
* fetchRandomStyleColor() selects colors.
166+
*
167+
* This method is thread safe.
168+
*
169+
* \see randomStyleColorScheme()
170+
* \see setRandomStyleColorScheme()
171+
* \since QGIS 3.2
172+
*/
173+
QColor fetchRandomStyleColor() const;
174+
122175
private:
123176

124177
QList< QgsColorScheme * > mColorSchemeList;
125178

179+
QgsColorScheme *mRandomStyleColorScheme = nullptr;
180+
QgsNamedColorList mRandomStyleColors;
181+
182+
mutable int mNextRandomStyleColorIndex = 0;
183+
184+
int mNextRandomStyleColorDirection = 1;
185+
126186
};
127187

128188

src/core/symbology/qgssymbol.cpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
#include "qgspolygon.h"
4040
#include "qgsclipper.h"
4141
#include "qgsproperty.h"
42+
#include "qgscolorschemeregistry.h"
4243

4344
#include <QColor>
4445
#include <QImage>
@@ -322,13 +323,7 @@ QgsSymbol *QgsSymbol::defaultSymbol( QgsWkbTypes::GeometryType geomType )
322323
if ( defaultSymbol.isEmpty() ||
323324
QgsProject::instance()->readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) )
324325
{
325-
// Make sure we use get uniquely seeded random numbers, and not the same sequence of numbers
326-
std::random_device rd;
327-
std::mt19937 mt( rd() );
328-
std::uniform_int_distribution<int> hueDist( 0, 359 );
329-
std::uniform_int_distribution<int> satDist( 64, 255 );
330-
std::uniform_int_distribution<int> valueDist( 128, 255 );
331-
s->setColor( QColor::fromHsv( hueDist( mt ), satDist( mt ), valueDist( mt ) ) );
326+
s->setColor( QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor() );
332327
}
333328

334329
return s;

tests/src/core/testqgscolorschemeregistry.cpp

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,29 @@ class DummyColorScheme : public QgsColorScheme
5656

5757
};
5858

59+
class DummyColorScheme2 : public QgsColorScheme
60+
{
61+
public:
62+
63+
DummyColorScheme2() = default;
64+
65+
QString schemeName() const override { return QStringLiteral( "Dummy scheme2" ); }
66+
67+
QgsNamedColorList fetchColors( const QString & = QString(),
68+
const QColor & = QColor() ) override
69+
{
70+
QList< QPair< QColor, QString> > colors;
71+
colors << qMakePair( QColor( 255, 255, 0 ), QStringLiteral( "schemetest" ) );
72+
return colors;
73+
}
74+
75+
QgsColorScheme *clone() const override
76+
{
77+
return new DummyColorScheme2();
78+
}
79+
80+
};
81+
5982
class TestQgsColorSchemeRegistry : public QObject
6083
{
6184
Q_OBJECT
@@ -73,6 +96,7 @@ class TestQgsColorSchemeRegistry : public QObject
7396
void populateFromInstance(); // check populating an empty scheme from the registry
7497
void removeScheme(); // check removing a scheme from a registry
7598
void matchingSchemes(); //check fetching schemes of specific type
99+
void fetchRandomStyleColor();
76100

77101
private:
78102

@@ -185,5 +209,47 @@ void TestQgsColorSchemeRegistry::matchingSchemes()
185209
QCOMPARE( dummySchemes.at( 0 ), dummyScheme );
186210
}
187211

212+
void TestQgsColorSchemeRegistry::fetchRandomStyleColor()
213+
{
214+
std::unique_ptr<QgsColorSchemeRegistry> registry = qgis::make_unique< QgsColorSchemeRegistry >();
215+
216+
// no randomStyleColorScheme set - test lots of colors to make sure their valid
217+
for ( int i = 0; i < 10000; ++i )
218+
{
219+
QVERIFY( registry->fetchRandomStyleColor().isValid() );
220+
}
221+
222+
// set a randomStyleColorScheme
223+
DummyColorScheme2 *dummyScheme = new DummyColorScheme2();
224+
registry->addColorScheme( dummyScheme );
225+
registry->setRandomStyleColorScheme( dummyScheme );
226+
227+
// only one color in scheme
228+
229+
for ( int i = 0; i < 10; ++i )
230+
{
231+
QCOMPARE( registry->fetchRandomStyleColor().name(), QStringLiteral( "#ffff00" ) );
232+
}
233+
234+
DummyColorScheme *dummyScheme2 = new DummyColorScheme();
235+
registry->addColorScheme( dummyScheme2 );
236+
registry->setRandomStyleColorScheme( dummyScheme2 );
237+
for ( int i = 0; i < 10; ++i )
238+
{
239+
QString color = registry->fetchRandomStyleColor().name();
240+
QVERIFY( color == QStringLiteral( "#ff0000" ) || color == QStringLiteral( "#00ff00" ) );
241+
}
242+
243+
// remove current random style color scheme
244+
registry->removeColorScheme( dummyScheme2 );
245+
QVERIFY( !registry->randomStyleColorScheme() );
246+
// no crash!
247+
for ( int i = 0; i < 10; ++i )
248+
{
249+
QVERIFY( registry->fetchRandomStyleColor().isValid() );
250+
}
251+
252+
}
253+
188254
QGSTEST_MAIN( TestQgsColorSchemeRegistry )
189255
#include "testqgscolorschemeregistry.moc"

0 commit comments

Comments
 (0)