Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Expression] add project/ramp_color_object functions #58578

Merged
merged 5 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,14 @@
"to_string", "tostring", "to_datetime", "todatetime", "to_date", "todate", "to_time", "totime", "to_interval", "tointerval",
"regexp_match", "now", "_now", "age", "year", "month", "week", "day", "hour", "minute", "second", "day_of_week", "title",
"levenshtein", "longest_common_substring", "hamming_distance", "wordwrap", "regexp_replace", "regexp_substr", "concat",
"strpos", "_left", "_right", "rpad", "lpad", "format", "format_number", "format_date", "color_rgb", "color_rgba", "ramp_color",
"color_hsl", "color_hsla", "color_hsv", "color_hsva", "color_cmyk", "color_cmyka", "color_part", "darker", "lighter",
"strpos", "_left", "_right", "rpad", "lpad", "format", "format_number", "format_date", "color_rgb", "color_rgba", "color_rgbf", "ramp_color", "ramp_color_object",
"color_hsl", "color_hsla", "color_hslf", "color_hsv", "color_hsva", "color_hsvf", "color_cmyk", "color_cmyka", "color_cmykf", "color_part", "darker", "lighter",
"set_color_part", "point_n", "start_point", "end_point", "nodes_to_points", "segments_to_lines", "make_point",
"make_point_m", "make_line", "make_polygon", "x_min", "xmin", "x_max", "xmax", "y_min", "ymin", "y_max", "ymax", "geom_from_wkt",
"geomFromWKT", "geom_from_gml", "relate", "intersects_bbox", "bbox", "translate", "buffer", "point_on_surface", "reverse",
"exterior_ring", "interior_ring_n", "geometry_n", "bounds", "num_points", "num_interior_rings", "num_rings", "num_geometries",
"bounds_width", "bounds_height", "is_closed", "convex_hull", "sym_difference", "combine", "_union", "geom_to_wkt", "geomToWKT",
"transform", "uuid", "_uuid", "layer_property", "var", "_specialcol_", "project_color"]
"transform", "uuid", "_uuid", "layer_property", "var", "_specialcol_", "project_color", "project_color_object"]


# constants
Expand Down
15 changes: 15 additions & 0 deletions resources/function_help/json/project_color_object
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "project_color_object",
"type": "function",
"groups": ["Color"],
"description": "Returns a color from the project's color scheme. Contrary to project_color which returns a color string representation, project_color_object returns a color object.",
"arguments": [{
"arg": "name",
"description": "a color name"
}],
"examples": [{
"expression": "project_color_object('Logo color')",
"returns": "RGBA: 0.08,0.55,0.20,1.00"
}],
"tags": ["scheme", "project", "color"]
}
37 changes: 37 additions & 0 deletions resources/function_help/json/ramp_color_object
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "ramp_color_object",
"type": "function",
"groups": ["Color"],
"description": "Returns a color object from a color ramp. Contrary to ramp_color which returns a color string representation, ramp_color_object returns a color object.",
"variants": [{
"variant": "Saved ramp variant",
"variant_description": "Returns a color object from a saved ramp",
"arguments": [{
"arg": "ramp_name",
"description": "the name of the color ramp as a string, for example 'Spectral'"
}, {
"arg": "value",
"description": "the position on the ramp to select the color from as a real number between 0 and 1"
}],
"examples": [{
"expression": "ramp_color_object('Spectral',0.3)",
"returns": "RGBA: 0.99,0.75,0.45,1.00"
}],
"notes": "The color ramps available vary between QGIS installations. This function may not give the expected results if you move your QGIS project between installations."
}, {
"variant": "Expression-created ramp variant",
"variant_description": "Returns a color object from an expression-created ramp",
"arguments": [{
"arg": "ramp",
"description": "the color ramp"
}, {
"arg": "value",
"description": "the position on the ramp to select the color from as a real number between 0 and 1"
}],
"examples": [{
"expression": "ramp_color_object(create_ramp(map(0,color_rgbf(0,0,0),1,color_rgbf(1,0,0))),1)",
"returns": "RGBA: 1.00,0.00,0.00,1.00"
}]
}],
"tags": ["ramp", "color"]
}
2 changes: 1 addition & 1 deletion src/core/expression/qgsexpressioncontextutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -954,6 +954,7 @@ QgsExpressionContextScope *QgsExpressionContextUtils::notificationScope( const Q
void QgsExpressionContextUtils::registerContextFunctions()
{
QgsExpression::registerFunction( new GetNamedProjectColor( nullptr ) );
QgsExpression::registerFunction( new GetNamedProjectColorObject( nullptr ) );
QgsExpression::registerFunction( new GetSensorData( ) );
QgsExpression::registerFunction( new GetLayoutItemVariables( nullptr ) );
QgsExpression::registerFunction( new GetLayoutMapLayerCredits( nullptr ) );
Expand Down Expand Up @@ -1422,4 +1423,3 @@ QgsScopedExpressionFunction *LoadLayerFunction::clone() const
return new LoadLayerFunction();
}
///@endcond

13 changes: 11 additions & 2 deletions src/core/expression/qgsexpressionfunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6004,7 +6004,7 @@ static QVariant fncColorRgba( const QVariantList &values, const QgsExpressionCon
return QgsSymbolLayerUtils::encodeColor( color );
}

QVariant fcnRampColor( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
QVariant fcnRampColorObject( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
{
QgsGradientColorRamp expRamp;
const QgsColorRamp *ramp = nullptr;
Expand All @@ -6026,7 +6026,13 @@ QVariant fcnRampColor( const QVariantList &values, const QgsExpressionContext *,

double value = QgsExpressionUtils::getDoubleValue( values.at( 1 ), parent );
QColor color = ramp->color( value );
return QgsSymbolLayerUtils::encodeColor( color );
return color;
}

QVariant fcnRampColor( const QVariantList &values, const QgsExpressionContext *context, QgsExpression *parent, const QgsExpressionNodeFunction *node )
{
QColor color = fcnRampColorObject( values, context, parent, node ).value<QColor>();
return color.isValid() ? QgsSymbolLayerUtils::encodeColor( color ) : QVariant();
}

static QVariant fcnColorHsl( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * )
Expand Down Expand Up @@ -8503,6 +8509,9 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()
<< new QgsStaticExpressionFunction( QStringLiteral( "ramp_color" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "ramp_name" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ),
fcnRampColor, QStringLiteral( "Color" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "ramp_color_object" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "ramp_name" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ),
fcnRampColorObject, QStringLiteral( "Color" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "create_ramp" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "map" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "discrete" ), true, false ),
fcnCreateRamp, QStringLiteral( "Color" ) )
Expand Down
53 changes: 48 additions & 5 deletions src/core/project/qgsproject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2915,6 +2915,7 @@ QgsExpressionContextScope *QgsProject::createExpressionContextScope() const
mProjectScope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layers" ), layers, true ) );

mProjectScope->addFunction( QStringLiteral( "project_color" ), new GetNamedProjectColor( this ) );
mProjectScope->addFunction( QStringLiteral( "project_color_object" ), new GetNamedProjectColorObject( this ) );

return createExpressionContextScope();
}
Expand Down Expand Up @@ -5267,11 +5268,10 @@ void QgsProject::loadProjectFlags( const QDomDocument *doc )
}

/// @cond PRIVATE
GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
: QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )

QHash< QString, QColor > loadColorsFromProject( const QgsProject *project )
{
if ( !project )
return;
QHash< QString, QColor > colors;

//build up color list from project. Do this in advance for speed
QStringList colorStrings = project->readListEntry( QStringLiteral( "Palette" ), QStringLiteral( "/Colors" ) );
Expand All @@ -5289,9 +5289,21 @@ GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
label = colorLabels.at( colorIndex );
}

mColors.insert( label.toLower(), color );
colors.insert( label.toLower(), color );
colorIndex++;
}

return colors;
}


GetNamedProjectColor::GetNamedProjectColor( const QgsProject *project )
: QgsScopedExpressionFunction( QStringLiteral( "project_color" ), 1, QStringLiteral( "Color" ) )
{
if ( !project )
return;

mColors = loadColorsFromProject( project );
}

GetNamedProjectColor::GetNamedProjectColor( const QHash<QString, QColor> &colors )
Expand All @@ -5316,6 +5328,37 @@ QgsScopedExpressionFunction *GetNamedProjectColor::clone() const
return new GetNamedProjectColor( mColors );
}

GetNamedProjectColorObject::GetNamedProjectColorObject( const QgsProject *project )
: QgsScopedExpressionFunction( QStringLiteral( "project_color_object" ), 1, QStringLiteral( "Color" ) )
{
if ( !project )
return;

mColors = loadColorsFromProject( project );
}

GetNamedProjectColorObject::GetNamedProjectColorObject( const QHash<QString, QColor> &colors )
: QgsScopedExpressionFunction( QStringLiteral( "project_color_object" ), 1, QStringLiteral( "Color" ) )
, mColors( colors )
{
}

QVariant GetNamedProjectColorObject::func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * )
{
const QString colorName = values.at( 0 ).toString().toLower();
if ( mColors.contains( colorName ) )
{
return mColors.value( colorName );
}
else
return QVariant();
}

QgsScopedExpressionFunction *GetNamedProjectColorObject::clone() const
{
return new GetNamedProjectColorObject( mColors );
}

// ----------------

GetSensorData::GetSensorData( const QMap<QString, QgsAbstractSensor::SensorData> &sensorData )
Expand Down
20 changes: 20 additions & 0 deletions src/core/project/qgsproject.h
Original file line number Diff line number Diff line change
Expand Up @@ -2563,9 +2563,29 @@ class GetNamedProjectColor : public QgsScopedExpressionFunction
private:

QHash< QString, QColor > mColors;
};

class GetNamedProjectColorObject : public QgsScopedExpressionFunction
{
public:
GetNamedProjectColorObject( const QgsProject *project );

/**
* Optimized constructor for GetNamedProjectColor when a list of map is already available
* and does not need to be read from a project.
*/
GetNamedProjectColorObject( const QHash< QString, QColor > &colors );

QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) override;
QgsScopedExpressionFunction *clone() const override;

private:

QHash< QString, QColor > mColors;
};



class GetSensorData : public QgsScopedExpressionFunction
{
public:
Expand Down
3 changes: 2 additions & 1 deletion src/core/qgscolorscheme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "qgssymbollayerutils.h"
#include "qgsapplication.h"
#include "qgssettings.h"
#include "qgscolorutils.h"

#include <QDir>
#include <QRegularExpression>
Expand Down Expand Up @@ -206,7 +207,7 @@ QgsNamedColorList QgsProjectColorScheme::fetchColors( const QString &context, co
for ( QStringList::iterator it = colorStrings.begin();
it != colorStrings.end(); ++it )
{
const QColor color = QgsSymbolLayerUtils::decodeColor( *it );
const QColor color = QgsColorUtils::colorFromString( *it );
QString label;
if ( colorLabels.length() > colorIndex )
{
Expand Down
2 changes: 1 addition & 1 deletion src/core/qgsproperty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ QSet<QString> QgsProperty::referencedFields( const QgsExpressionContext &context

bool QgsProperty::isProjectColor() const
{
const thread_local QRegularExpression rx( QStringLiteral( "^project_color\\('.*'\\)$" ) );
const thread_local QRegularExpression rx( QStringLiteral( "^project_color(_object|)\\('.*'\\)$" ) );
return d->type == Qgis::PropertyType::Expression && !d->expressionString.isEmpty()
&& rx.match( d->expressionString ).hasMatch();
}
Expand Down
27 changes: 16 additions & 11 deletions src/gui/qgspropertyoverridebutton.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ void QgsPropertyOverrideButton::aboutToShowMenu()
QPixmap icon = QgsColorButton::createMenuIcon( color.first, mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha );
QAction *act = mColorsMenu->addAction( color.second );
act->setIcon( icon );
if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && mExpressionString == QStringLiteral( "project_color('%1')" ).arg( color.second ) )
if ( mProperty.propertyType() == Qgis::PropertyType::Expression && hasExp && getColor() == color.second )
{
act->setCheckable( true );
act->setChecked( true );
Expand Down Expand Up @@ -643,9 +643,9 @@ void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
}
else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
{
if ( mExpressionString != QStringLiteral( "project_color('%1')" ).arg( action->text() ) )
if ( getColor() != action->text() )
{
mExpressionString = QStringLiteral( "project_color('%1')" ).arg( action->text() );
mExpressionString = QStringLiteral( "project_color_object('%1')" ).arg( action->text() );
}
mProperty.setExpressionString( mExpressionString );
mProperty.setTransformer( nullptr );
Expand Down Expand Up @@ -769,12 +769,11 @@ void QgsPropertyOverrideButton::updateGui()
{
icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpressionOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineExpression.svg" ) );

const thread_local QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
QRegularExpressionMatch match = rx.match( mExpressionString );
if ( match.hasMatch() )
const QString colorName = getColor();
if ( !colorName.isEmpty() )
{
icon = mProperty.isActive() ? QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColorOn.svg" ) ) : QgsApplication::getThemeIcon( QStringLiteral( "/mIconDataDefineColor.svg" ) );
deftip = match.captured( 1 );
deftip = colorName;
deftype = tr( "project color" );
}
else
Expand Down Expand Up @@ -925,11 +924,10 @@ void QgsPropertyOverrideButton::updateSiblingWidgets( bool state )
{
if ( state && mProperty.isProjectColor() )
{
const thread_local QRegularExpression rx( QStringLiteral( "^project_color\\('(.*)'\\)$" ) );
QRegularExpressionMatch match = rx.match( mExpressionString );
if ( match.hasMatch() )
const QString colorName = getColor();
if ( !colorName.isEmpty() )
{
cb->linkToProjectColor( match.captured( 1 ) );
cb->linkToProjectColor( colorName );
}
}
else
Expand Down Expand Up @@ -986,3 +984,10 @@ void QgsPropertyOverrideButton::showHelp()
{
QgsHelp::openHelp( QStringLiteral( "introduction/general_tools.html#data-defined" ) );
}

QString QgsPropertyOverrideButton::getColor() const
{
const thread_local QRegularExpression rx( QStringLiteral( "^project_color(_object|)\\('(.*)'\\)$" ) );
QRegularExpressionMatch match = rx.match( mExpressionString );
return match.hasMatch() ? match.captured( 2 ) : QString();
}
2 changes: 2 additions & 0 deletions src/gui/qgspropertyoverridebutton.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ class GUI_EXPORT QgsPropertyOverrideButton: public QToolButton
*/
void setActivePrivate( bool active );

// Returns color name if current expression is a reference to a color
QString getColor() const;

int mPropertyKey = -1;

Expand Down
3 changes: 3 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1879,6 +1879,9 @@ class TestQgsExpression: public QObject

// Color functions
QTest::newRow( "ramp color" ) << "ramp_color('Spectral',0.3)" << false << QVariant( "253,190,116,255" );
QTest::newRow( "ramp color error" ) << "ramp_color('NotExistingRamp',0.3)" << true << QVariant();
QTest::newRow( "ramp color object" ) << "ramp_color_object('Spectral',0.3)" << false << QVariant( QColor::fromRgbF( 0.994, 0.746, 0.454 ) );
QTest::newRow( "ramp color object error" ) << "ramp_color_object('NotExistingRamp',0.3)" << true << QVariant();
QTest::newRow( "create ramp color, wrong parameter" ) << "create_ramp(1)" << true << QVariant();
QTest::newRow( "create ramp color, no color" ) << "create_ramp(map())" << true << QVariant();
QTest::newRow( "create ramp color, one color" ) << "ramp_color(create_ramp(map(0,'0,0,0')),0.5)" << false << QVariant( "0,0,0,255" );
Expand Down
11 changes: 11 additions & 0 deletions tests/src/core/testqgsexpressioncontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,7 @@ void TestQgsExpressionContext::projectScope()
QgsNamedColorList colorList;
colorList << qMakePair( QColor( 200, 255, 0 ), QStringLiteral( "vomit yellow" ) );
colorList << qMakePair( QColor( 30, 60, 20 ), QStringLiteral( "murky depths of hades" ) );
colorList << qMakePair( QColor::fromCmykF( 1., 0.9, 0.8, 0.7 ), QStringLiteral( "cmyk colors" ) );
s.setColors( colorList );
QgsExpressionContext contextColors;
contextColors << QgsExpressionContextUtils::projectScope( QgsProject::instance() );
Expand All @@ -823,6 +824,16 @@ void TestQgsExpressionContext::projectScope()
QCOMPARE( expProjectColorCaseInsensitive.evaluate( &contextColors ).toString(), QString( "30,60,20" ) );
QgsExpression badProjectColor( QStringLiteral( "project_color('dusk falls in san juan del sur')" ) );
QCOMPARE( badProjectColor.evaluate( &contextColors ), QVariant() );

QgsExpression expProjectColorObject( QStringLiteral( "project_color_object('murky depths of hades')" ) );
QCOMPARE( expProjectColorObject.evaluate( &contextColors ), QVariant( QColor::fromRgb( 30, 60, 20 ) ) );
//matching color names should be case insensitive
QgsExpression expProjectColorObjectCaseInsensitive( QStringLiteral( "project_color_object('Murky Depths of hades')" ) );
QCOMPARE( expProjectColorObjectCaseInsensitive.evaluate( &contextColors ), QVariant( QColor::fromRgb( 30, 60, 20 ) ) );
QgsExpression expProjectColorCmyk( QStringLiteral( "project_color_object('cmyk colors')" ) );
QCOMPARE( expProjectColorCmyk.evaluate( &contextColors ), QVariant( QColor::fromCmykF( 1., 0.9, 0.8, 0.7 ) ) );
QgsExpression badProjectColorObject( QStringLiteral( "project_color_object('dusk falls in san juan del sur')" ) );
QCOMPARE( badProjectColorObject.evaluate( &contextColors ), QVariant() );
}

void TestQgsExpressionContext::layerScope()
Expand Down
4 changes: 4 additions & 0 deletions tests/src/core/testqgsproperty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1946,6 +1946,10 @@ void TestQgsProperty::isProjectColor()
QVERIFY( p.isProjectColor() );
p = QgsProperty::fromExpression( QStringLiteral( "project_color('burnt pineapple Skin 76')" ), true );
QVERIFY( p.isProjectColor() );
p = QgsProperty::fromExpression( QStringLiteral( "project_color_object('mine')" ), true );
QVERIFY( p.isProjectColor() );
p = QgsProperty::fromExpression( QStringLiteral( "project_color_object('burnt pineapple Skin 76')" ), true );
QVERIFY( p.isProjectColor() );
p.setActive( false );
QVERIFY( p.isProjectColor() );
}
Expand Down
Loading
Loading