Skip to content
Permalink
Browse files
Add parameterized color support to QgsApplication::getThemeIcon
Adds framework to allow for icons which can have dynamic coloring
  • Loading branch information
nyalldawson committed May 17, 2021
1 parent ff2b8dc commit cd6aa7f6657e663900e782ddeb26ec7d6ecb0476
@@ -916,6 +916,8 @@
<file>themes/default/mActionEditHtml.svg</file>
<file>themes/default/mIndicatorNotes.svg</file>
<file>themes/default/mIndicatorLowAccuracy.svg</file>
<file>themes/default/mIconFolderParams.svg</file>
<file>themes/default/mIconFolderOpenParams.svg</file>
</qresource>
<qresource prefix="/images/tips">
<file alias="symbol_levels.png">qgis_tips/symbol_levels.png</file>
@@ -0,0 +1,2 @@
<svg height="16" width="16" xmlns="http://www.w3.org/2000/svg"><g stroke="param(outline) #888a85" stroke-opacity="param(outline-opacity) 1"><path d="m1.5 1.5v2h6.75l2 2h4.25v-2h-4.25l-2-2z" fill="param(outline) #888a85" fill-opacity="param(outline-opacity) 1" /><g fill="param(fill) #eee" fill-opacity="param(fill-opacity) 1"><path d="m1.5 5.5v8.25s-.01.75.75.75h11.5s.75.02.75-.75v-6.25h-5.25l-2-2z"/><path d="m1.5 5.5v8.25s.019.75.75.75h11.5s.749 0 .749-.75l.001-6.25h-5.25l-2-2z"/></g></g></svg>

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M1.5 2.501V13.75s.019.75.75.75h11.5s.749 0 .749-.75L14.5 4.5H9.2l-2-2H1.5z" fill="param(fill) #eee" fill-opacity="param(fill-opacity) 1" stroke="param(outline) #888a85" stroke-opacity="param(outline-opacity) 1" /></svg>
@@ -337,10 +337,13 @@ Returns path to the desired icon file.
First it tries to use the active theme path, then default theme path
%End

static QIcon getThemeIcon( const QString &name );
static QIcon getThemeIcon( const QString &name, const QColor &fillColor = QColor(), const QColor &strokeColor = QColor() );
%Docstring
Helper to get a theme icon. It will fall back to the
default theme if the active theme does not have the required icon.

Since QGIS 3.20, the optional ``fillColor`` and ``strokeColor`` arguments can be used to
control the color of parameter based SVG icons.
%End

enum Cursor
@@ -646,33 +646,62 @@ QString QgsApplication::iconPath( const QString &iconFile )
return defaultThemePath() + iconFile;
}

QIcon QgsApplication::getThemeIcon( const QString &name )
QIcon QgsApplication::getThemeIcon( const QString &name, const QColor &fillColor, const QColor &strokeColor )
{
const QString cacheKey = name
+ ( fillColor.isValid() ? QStringLiteral( ":%1" ).arg( fillColor.name( QColor::HexArgb ) ) : QString() )
+ ( strokeColor.isValid() ? QStringLiteral( ":%1" ).arg( strokeColor.name( QColor::HexArgb ) ) : QString() );
QgsApplication *app = instance();
if ( app && app->mIconCache.contains( name ) )
return app->mIconCache.value( name );
if ( app && app->mIconCache.contains( cacheKey ) )
return app->mIconCache.value( cacheKey );

QIcon icon;
const bool colorBased = fillColor.isValid() || strokeColor.isValid();

QString myPreferredPath = activeThemePath() + QDir::separator() + name;
QString myDefaultPath = defaultThemePath() + QDir::separator() + name;
if ( QFile::exists( myPreferredPath ) )
auto iconFromColoredSvg = [ = ]( const QString & path ) -> QIcon
{
icon = QIcon( myPreferredPath );
// sizes are unused here!
const QByteArray svgContent = QgsApplication::svgCache()->svgContent( path, 16, fillColor, strokeColor, 1, 1 );
QTemporaryFile f;
if ( f.open() )
f.write( svgContent );
f.close();
return QIcon( f.fileName() );
};

QString preferredPath = activeThemePath() + QDir::separator() + name;
QString defaultPath = defaultThemePath() + QDir::separator() + name;
if ( QFile::exists( preferredPath ) )
{
if ( colorBased )
{
icon = iconFromColoredSvg( preferredPath );
}
else
{
icon = QIcon( preferredPath );
}
}
else if ( QFile::exists( myDefaultPath ) )
else if ( QFile::exists( defaultPath ) )
{
//could still return an empty icon if it
//doesn't exist in the default theme either!
icon = QIcon( myDefaultPath );
if ( colorBased )
{
icon = iconFromColoredSvg( defaultPath );
}
else
{
icon = QIcon( defaultPath );
}
}
else
{
icon = QIcon();
}

if ( app )
app->mIconCache.insert( name, icon );
app->mIconCache.insert( cacheKey, icon );
return icon;
}

@@ -362,8 +362,11 @@ class CORE_EXPORT QgsApplication : public QApplication
/**
* Helper to get a theme icon. It will fall back to the
* default theme if the active theme does not have the required icon.
*
* Since QGIS 3.20, the optional \a fillColor and \a strokeColor arguments can be used to
* control the color of parameter based SVG icons.
*/
static QIcon getThemeIcon( const QString &name );
static QIcon getThemeIcon( const QString &name, const QColor &fillColor = QColor(), const QColor &strokeColor = QColor() );

/**
* \brief The Cursor enum defines constants for QGIS custom
@@ -1069,6 +1072,8 @@ class CORE_EXPORT QgsApplication : public QApplication
static ApplicationMembers *members();

static void invalidateCaches();

friend class TestQgsApplication;
};

// clazy:excludeall=qstring-allocations
@@ -20,6 +20,7 @@ Email : sherman at mrcc dot com

//header for class being tested
#include <qgsapplication.h>
#include "qgsrenderchecker.h"

class TestQgsApplication: public QObject
{
@@ -33,9 +34,12 @@ class TestQgsApplication: public QObject
void accountName();
void osName();
void platformName();
void themeIcon();

private:
QString getQgisPath();
bool renderCheck( const QString &testName, QImage &image, int mismatchCount = 0 );
QString mReport;
};


@@ -48,10 +52,21 @@ void TestQgsApplication::initTestCase()
QgsApplication::init();
QgsApplication::initQgis();
qDebug( "%s", QgsApplication::showSettings().toUtf8().constData() );

mReport = QStringLiteral( "<h1>QgsApplication Tests</h1>\n" );

}

void TestQgsApplication::cleanupTestCase()
{
QString myReportFile = QDir::tempPath() + QDir::separator() + "qgistest.html";
QFile myFile( myReportFile );
if ( myFile.open( QIODevice::WriteOnly | QIODevice::Append ) )
{
QTextStream myQTextStream( &myFile );
myQTextStream << mReport;
myFile.close();
}
QgsApplication::exitQgis();
}

@@ -83,6 +98,40 @@ void TestQgsApplication::platformName()
QCOMPARE( QgsApplication::platform(), QString( "desktop" ) );
}

void TestQgsApplication::themeIcon()
{
QIcon icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconFolder.svg" ) );
QVERIFY( !icon.isNull() );
QImage im( icon.pixmap( 16, 16 ).toImage() );
QVERIFY( renderCheck( QStringLiteral( "theme_icon" ), im, 0 ) );

// parameterized
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconFolderParams.svg" ), QColor( 255, 100, 100 ), QColor( 255, 0, 0 ) );
QVERIFY( !icon.isNull() );
im = QImage( icon.pixmap( 16, 16 ).toImage() );
QVERIFY( renderCheck( QStringLiteral( "theme_icon_colors_1" ), im, 0 ) );
// different colors
icon = QgsApplication::getThemeIcon( QStringLiteral( "/mIconFolderParams.svg" ), QColor( 170, 255, 170 ), QColor( 0, 255, 0 ) );
QVERIFY( !icon.isNull() );
im = QImage( icon.pixmap( 16, 16 ).toImage() );
QVERIFY( renderCheck( QStringLiteral( "theme_icon_colors_2" ), im, 0 ) );
}

bool TestQgsApplication::renderCheck( const QString &testName, QImage &image, int mismatchCount )
{
mReport += "<h2>" + testName + "</h2>\n";
QString myTmpDir = QDir::tempPath() + '/';
QString myFileName = myTmpDir + testName + ".png";
image.save( myFileName, "PNG" );
QgsRenderChecker myChecker;
myChecker.setControlPathPrefix( QStringLiteral( "application" ) );
myChecker.setControlName( "expected_" + testName );
myChecker.setRenderedImage( myFileName );
bool myResultFlag = myChecker.compareImages( testName, mismatchCount );
mReport += myChecker.report();
return myResultFlag;
}

void TestQgsApplication::checkPaths()
{
QString myPath = QgsApplication::authorsFilePath();
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit cd6aa7f

Please sign in to comment.