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.
This commit is contained in:
Nyall Dawson 2018-02-05 15:11:42 +10:00
parent 40ceb7bdce
commit bc449c401b
5 changed files with 246 additions and 8 deletions

View File

@ -104,6 +104,63 @@ Returns all color schemes in the registry which have a specified flag set
void setRandomStyleColorScheme( QgsColorScheme *scheme );
%Docstring
Sets the color ``scheme`` to use when fetching random colors to use for symbol styles.
``scheme`` should match a color scheme which is already present in the registry.
Note that calling this method takes a snapshot of the colors from the scheme's
QgsColorScheme.fetchColors() list. Accordingly, any future changes to the colors
in ``scheme`` are not automatically reflected by calls to fetchRandomStyleColor().
If ``scheme`` is updated, then another call to setRandomStyleColorScheme() must
be made in order to update the cached list of available style colors.
.. seealso:: :py:func:`randomStyleColorScheme`
.. seealso:: :py:func:`fetchRandomStyleColor`
.. versionadded:: 3.2
%End
QgsColorScheme *randomStyleColorScheme();
%Docstring
Returns the color scheme used when fetching random colors to use for symbol styles.
This may be None, in which case totally random colors are used for styles.
.. seealso:: :py:func:`setRandomStyleColorScheme`
.. seealso:: :py:func:`fetchRandomStyleColor`
.. versionadded:: 3.2
%End
QColor fetchRandomStyleColor() const;
%Docstring
Returns a random color for use with a new symbol style (e.g. for a newly created
map layer).
If a randomStyleColorScheme() is set then this color will be randomly taken from that
color scheme. If no randomStyleColorScheme() is set then a totally random color
will be generated.
Note that calling setRandomStyleColorScheme() takes a snapshot of the colors from the scheme's
QgsColorScheme.fetchColors() list. Accordingly, any future changes to the colors
in the scheme are not automatically reflected by calls to fetchRandomStyleColor().
If the scheme is updated, then another call to setRandomStyleColorScheme() must
be made in order to update the cached list of available style colors from which
fetchRandomStyleColor() selects colors.
This method is thread safe.
.. seealso:: :py:func:`randomStyleColorScheme`
.. seealso:: :py:func:`setRandomStyleColorScheme`
.. versionadded:: 3.2
%End
};

View File

@ -20,7 +20,8 @@
#include "qgsapplication.h"
#include <QDir>
#include <QFileInfoList>
#include <QMutex>
#include <random>
QgsColorSchemeRegistry::~QgsColorSchemeRegistry()
{
@ -98,8 +99,67 @@ QList<QgsColorScheme *> QgsColorSchemeRegistry::schemes( const QgsColorScheme::S
return matchingSchemes;
}
void QgsColorSchemeRegistry::setRandomStyleColorScheme( QgsColorScheme *scheme )
{
mRandomStyleColorScheme = scheme;
if ( scheme )
{
mRandomStyleColors = scheme->fetchColors();
std::random_device rd;
std::mt19937 mt( rd() );
std::uniform_int_distribution<int> colorDist( 0, mRandomStyleColors.count() - 1 );
mNextRandomStyleColorIndex = colorDist( mt );
std::uniform_int_distribution<int> colorDir( 0, 1 );
mNextRandomStyleColorDirection = colorDir( mt ) == 0 ? -1 : 1;
}
else
{
mRandomStyleColors.clear();
}
}
QgsColorScheme *QgsColorSchemeRegistry::randomStyleColorScheme()
{
return mRandomStyleColorScheme;
}
QColor QgsColorSchemeRegistry::fetchRandomStyleColor() const
{
if ( mRandomStyleColors.empty() )
{
// no random color scheme available - so just use totally random colors
// Make sure we use get uniquely seeded random numbers, and not the same sequence of numbers
std::random_device rd;
std::mt19937 mt( rd() );
std::uniform_int_distribution<int> hueDist( 0, 359 );
std::uniform_int_distribution<int> satDist( 64, 255 );
std::uniform_int_distribution<int> valueDist( 128, 255 );
return QColor::fromHsv( hueDist( mt ), satDist( mt ), valueDist( mt ) );
}
else
{
static QMutex sMutex;
QMutexLocker locker( &sMutex );
QColor res = mRandomStyleColors.at( mNextRandomStyleColorIndex ).first;
mNextRandomStyleColorIndex += mNextRandomStyleColorDirection;
if ( mNextRandomStyleColorIndex < 0 )
mNextRandomStyleColorIndex = mRandomStyleColors.count() - 1;
if ( mNextRandomStyleColorIndex >= mRandomStyleColors.count() )
mNextRandomStyleColorIndex = 0;
return res;
}
}
bool QgsColorSchemeRegistry::removeColorScheme( QgsColorScheme *scheme )
{
if ( mRandomStyleColorScheme == scheme )
{
mRandomStyleColorScheme = nullptr;
mRandomStyleColors.clear();
}
if ( mColorSchemeList.indexOf( scheme ) != -1 )
{
mColorSchemeList.removeAll( scheme );

View File

@ -119,10 +119,70 @@ class CORE_EXPORT QgsColorSchemeRegistry
}
#endif
/**
* Sets the color \a scheme to use when fetching random colors to use for symbol styles.
*
* \a scheme should match a color scheme which is already present in the registry.
*
* Note that calling this method takes a snapshot of the colors from the scheme's
* QgsColorScheme::fetchColors() list. Accordingly, any future changes to the colors
* in \a scheme are not automatically reflected by calls to fetchRandomStyleColor().
* If \a scheme is updated, then another call to setRandomStyleColorScheme() must
* be made in order to update the cached list of available style colors.
*
* \see randomStyleColorScheme()
* \see fetchRandomStyleColor()
*
* \since QGIS 3.2
*/
void setRandomStyleColorScheme( QgsColorScheme *scheme );
/**
* Returns the color scheme used when fetching random colors to use for symbol styles.
*
* This may be nullptr, in which case totally random colors are used for styles.
*
* \see setRandomStyleColorScheme()
* \see fetchRandomStyleColor()
*
* \since QGIS 3.2
*/
QgsColorScheme *randomStyleColorScheme();
/**
* Returns a random color for use with a new symbol style (e.g. for a newly created
* map layer).
*
* If a randomStyleColorScheme() is set then this color will be randomly taken from that
* color scheme. If no randomStyleColorScheme() is set then a totally random color
* will be generated.
*
* Note that calling setRandomStyleColorScheme() takes a snapshot of the colors from the scheme's
* QgsColorScheme::fetchColors() list. Accordingly, any future changes to the colors
* in the scheme are not automatically reflected by calls to fetchRandomStyleColor().
* If the scheme is updated, then another call to setRandomStyleColorScheme() must
* be made in order to update the cached list of available style colors from which
* fetchRandomStyleColor() selects colors.
*
* This method is thread safe.
*
* \see randomStyleColorScheme()
* \see setRandomStyleColorScheme()
* \since QGIS 3.2
*/
QColor fetchRandomStyleColor() const;
private:
QList< QgsColorScheme * > mColorSchemeList;
QgsColorScheme *mRandomStyleColorScheme = nullptr;
QgsNamedColorList mRandomStyleColors;
mutable int mNextRandomStyleColorIndex = 0;
int mNextRandomStyleColorDirection = 1;
};

View File

@ -39,6 +39,7 @@
#include "qgspolygon.h"
#include "qgsclipper.h"
#include "qgsproperty.h"
#include "qgscolorschemeregistry.h"
#include <QColor>
#include <QImage>
@ -322,13 +323,7 @@ QgsSymbol *QgsSymbol::defaultSymbol( QgsWkbTypes::GeometryType geomType )
if ( defaultSymbol.isEmpty() ||
QgsProject::instance()->readBoolEntry( QStringLiteral( "DefaultStyles" ), QStringLiteral( "/RandomColors" ), true ) )
{
// Make sure we use get uniquely seeded random numbers, and not the same sequence of numbers
std::random_device rd;
std::mt19937 mt( rd() );
std::uniform_int_distribution<int> hueDist( 0, 359 );
std::uniform_int_distribution<int> satDist( 64, 255 );
std::uniform_int_distribution<int> valueDist( 128, 255 );
s->setColor( QColor::fromHsv( hueDist( mt ), satDist( mt ), valueDist( mt ) ) );
s->setColor( QgsApplication::colorSchemeRegistry()->fetchRandomStyleColor() );
}
return s;

View File

@ -56,6 +56,29 @@ class DummyColorScheme : public QgsColorScheme
};
class DummyColorScheme2 : public QgsColorScheme
{
public:
DummyColorScheme2() = default;
QString schemeName() const override { return QStringLiteral( "Dummy scheme2" ); }
QgsNamedColorList fetchColors( const QString & = QString(),
const QColor & = QColor() ) override
{
QList< QPair< QColor, QString> > colors;
colors << qMakePair( QColor( 255, 255, 0 ), QStringLiteral( "schemetest" ) );
return colors;
}
QgsColorScheme *clone() const override
{
return new DummyColorScheme2();
}
};
class TestQgsColorSchemeRegistry : public QObject
{
Q_OBJECT
@ -73,6 +96,7 @@ class TestQgsColorSchemeRegistry : public QObject
void populateFromInstance(); // check populating an empty scheme from the registry
void removeScheme(); // check removing a scheme from a registry
void matchingSchemes(); //check fetching schemes of specific type
void fetchRandomStyleColor();
private:
@ -185,5 +209,47 @@ void TestQgsColorSchemeRegistry::matchingSchemes()
QCOMPARE( dummySchemes.at( 0 ), dummyScheme );
}
void TestQgsColorSchemeRegistry::fetchRandomStyleColor()
{
std::unique_ptr<QgsColorSchemeRegistry> registry = qgis::make_unique< QgsColorSchemeRegistry >();
// no randomStyleColorScheme set - test lots of colors to make sure their valid
for ( int i = 0; i < 10000; ++i )
{
QVERIFY( registry->fetchRandomStyleColor().isValid() );
}
// set a randomStyleColorScheme
DummyColorScheme2 *dummyScheme = new DummyColorScheme2();
registry->addColorScheme( dummyScheme );
registry->setRandomStyleColorScheme( dummyScheme );
// only one color in scheme
for ( int i = 0; i < 10; ++i )
{
QCOMPARE( registry->fetchRandomStyleColor().name(), QStringLiteral( "#ffff00" ) );
}
DummyColorScheme *dummyScheme2 = new DummyColorScheme();
registry->addColorScheme( dummyScheme2 );
registry->setRandomStyleColorScheme( dummyScheme2 );
for ( int i = 0; i < 10; ++i )
{
QString color = registry->fetchRandomStyleColor().name();
QVERIFY( color == QStringLiteral( "#ff0000" ) || color == QStringLiteral( "#00ff00" ) );
}
// remove current random style color scheme
registry->removeColorScheme( dummyScheme2 );
QVERIFY( !registry->randomStyleColorScheme() );
// no crash!
for ( int i = 0; i < 10; ++i )
{
QVERIFY( registry->fetchRandomStyleColor().isValid() );
}
}
QGSTEST_MAIN( TestQgsColorSchemeRegistry )
#include "testqgscolorschemeregistry.moc"