Skip to content

Commit

Permalink
Fix some layout expression context handling, restore some tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Jan 5, 2018
1 parent 327d311 commit 1b93231
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 32 deletions.
3 changes: 3 additions & 0 deletions python/core/layout/qgslayoutitem.sip
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,9 @@ Rotates the item by a specified ``angle`` in degrees clockwise around a specifie
.. seealso:: :py:func:`itemRotation()`
%End

virtual QgsExpressionContext createExpressionContext() const;


signals:

void frameChanged();
Expand Down
1 change: 1 addition & 0 deletions python/core/layout/qgslayoutpagecollection.sip
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ A None is returned if an invalid page number is specified.
.. seealso:: :py:func:`pages()`
%End


int pageNumber( QgsLayoutItemPage *page ) const;
%Docstring
Returns the page number for the specified ``page``, or -1 if the page
Expand Down
10 changes: 9 additions & 1 deletion python/core/qgsexpressioncontext.sip
Original file line number Diff line number Diff line change
Expand Up @@ -1055,7 +1055,7 @@ For instance, current page name and number.

static QgsExpressionContextScope *atlasScope( const QgsLayoutAtlas *atlas ) /Factory/;
%Docstring
Creates a new scope which contains variables and functions relating to a QgsLayoutAtlas.
Creates a new scope which contains variables and functions relating to a :py:class:`QgsLayoutAtlas`.
For instance, current page name and number.

:param atlas: source atlas. If null, a set of default atlas variables will be added to the scope.
Expand All @@ -1067,6 +1067,14 @@ Creates a new scope which contains variables and functions relating to a :py:cla
For instance, item size and position.

:param composerItem: source composer item
%End

static QgsExpressionContextScope *layoutItemScope( const QgsLayoutItem *item ) /Factory/;
%Docstring
Creates a new scope which contains variables and functions relating to a :py:class:`QgsLayoutItem`.
For instance, item size and position.

.. versionadded:: 3.0
%End

static void setComposerItemVariable( QgsComposerItem *composerItem, const QString &name, const QVariant &value );
Expand Down
7 changes: 7 additions & 0 deletions src/core/layout/qgslayoutitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,13 @@ void QgsLayoutItem::rotateItem( const double angle, const QPointF &transformOrig
refreshItemRotation( &itemTransformOrigin );
}

QgsExpressionContext QgsLayoutItem::createExpressionContext() const
{
QgsExpressionContext context = QgsLayoutObject::createExpressionContext();
context.appendScope( QgsExpressionContextUtils::layoutItemScope( this ) );
return context;
}


void QgsLayoutItem::refresh()
{
Expand Down
2 changes: 2 additions & 0 deletions src/core/layout/qgslayoutitem.h
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,8 @@ class CORE_EXPORT QgsLayoutItem : public QgsLayoutObject, public QGraphicsRectIt
*/
virtual void rotateItem( const double angle, const QPointF &transformOrigin );

QgsExpressionContext createExpressionContext() const override;

signals:

/**
Expand Down
5 changes: 5 additions & 0 deletions src/core/layout/qgslayoutpagecollection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,11 @@ QgsLayoutItemPage *QgsLayoutPageCollection::page( int pageNumber )
return mPages.value( pageNumber );
}

const QgsLayoutItemPage *QgsLayoutPageCollection::page( int pageNumber ) const
{
return mPages.value( pageNumber );
}

int QgsLayoutPageCollection::pageNumber( QgsLayoutItemPage *page ) const
{
return mPages.indexOf( page );
Expand Down
10 changes: 10 additions & 0 deletions src/core/layout/qgslayoutpagecollection.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject, public QgsLayoutSeri
*/
QgsLayoutItemPage *page( int pageNumber );

/**
* Returns a specific page (by \a pageNumber) from the collection.
* Internal page numbering starts at 0 - so a \a pageNumber of 0
* corresponds to the first page in the collection.
* A nullptr is returned if an invalid page number is specified.
* \see pages()
* \note Not available in Python bindings.
*/
const QgsLayoutItemPage *page( int pageNumber ) const SIP_SKIP;

/**
* Returns the page number for the specified \a page, or -1 if the page
* is not contained in the collection.
Expand Down
116 changes: 108 additions & 8 deletions src/core/qgsexpressioncontext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "qgsprocessingalgorithm.h"
#include "qgslayoutatlas.h"
#include "qgslayout.h"
#include "qgslayoutpagecollection.h"

#include <QSettings>
#include <QDir>
Expand Down Expand Up @@ -700,6 +701,41 @@ class GetComposerItemVariables : public QgsScopedExpressionFunction

};

class GetLayoutItemVariables : public QgsScopedExpressionFunction
{
public:
GetLayoutItemVariables( const QgsLayout *c )
: QgsScopedExpressionFunction( QStringLiteral( "item_variables" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "id" ) ), QStringLiteral( "Layout" ) )
, mLayout( c )
{}

QVariant func( const QVariantList &values, const QgsExpressionContext *, QgsExpression *, const QgsExpressionNodeFunction * ) override
{
if ( !mLayout )
return QVariant();

QString id = values.at( 0 ).toString().toLower();

const QgsLayoutItem *item = mLayout->itemByUuid( id );
if ( !item )
return QVariant();

QgsExpressionContext c = item->createExpressionContext();

return c.variablesToMap();
}

QgsScopedExpressionFunction *clone() const override
{
return new GetLayoutItemVariables( mLayout );
}

private:

const QgsLayout *mLayout = nullptr;

};

class GetLayerVisibility : public QgsScopedExpressionFunction
{
public:
Expand Down Expand Up @@ -1079,16 +1115,34 @@ QgsExpressionContextScope *QgsExpressionContextUtils::layoutScope( const QgsLayo

//add known layout context variables
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_name" ), layout->name(), true ) );
#if 0 //TODO
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_numpages" ), composition->numPages(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pageheight" ), composition->paperHeight(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pagewidth" ), composition->paperWidth(), true ) );

scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_numpages" ), layout->pageCollection()->pageCount(), true ) );
if ( layout->pageCollection()->pageCount() > 0 )
{
// just take first page size
QSizeF s = layout->pageCollection()->page( 0 )->sizeWithUnits().toQSizeF();
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pageheight" ), s.height(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pagewidth" ), s.width(), true ) );
}
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_dpi" ), layout->context().dpi(), true ) );
#endif

#if 0 //TODO
scope->addFunction( QStringLiteral( "item_variables" ), new GetComposerItemVariables( composition ) );
#endif
scope->addFunction( QStringLiteral( "item_variables" ), new GetLayoutItemVariables( layout ) );

if ( layout->context().layer() )
{
scope->setFields( layout->context().layer()->fields() );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_layerid" ), layout->context().layer()->id(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_layername" ), layout->context().layer()->name(), true ) );
}

if ( layout->context().feature().isValid() )
{
QgsFeature atlasFeature = layout->context().feature();
scope->setFeature( atlasFeature );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_feature" ), QVariant::fromValue( atlasFeature ), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_featureid" ), atlasFeature.id(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "atlas_geometry" ), QVariant::fromValue( atlasFeature.geometry() ), true ) );
}

return scope.release();
}
Expand Down Expand Up @@ -1233,6 +1287,52 @@ QgsExpressionContextScope *QgsExpressionContextUtils::composerItemScope( const Q
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "item_id" ), composerItem->id(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "item_uuid" ), composerItem->uuid(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_page" ), composerItem->page(), true ) );
return scope;
}

QgsExpressionContextScope *QgsExpressionContextUtils::layoutItemScope( const QgsLayoutItem *item )
{
QgsExpressionContextScope *scope = new QgsExpressionContextScope( QObject::tr( "Layout Item" ) );
if ( !item )
return scope;

//add variables defined in composer item properties
const QStringList variableNames = item->customProperty( QStringLiteral( "variableNames" ) ).toStringList();
const QStringList variableValues = item->customProperty( QStringLiteral( "variableValues" ) ).toStringList();

int varIndex = 0;
for ( const QString &variableName : variableNames )
{
if ( varIndex >= variableValues.length() )
{
break;
}

QVariant varValue = variableValues.at( varIndex );
varIndex++;
scope->setVariable( variableName, varValue );
}

//add known composer item context variables
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "item_id" ), item->id(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "item_uuid" ), item->uuid(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_page" ), item->page() + 1, true ) );

if ( item->layout() )
{
const QgsLayoutItemPage *page = item->layout()->pageCollection()->page( item->page() );
if ( page )
{
const QSizeF s = page->sizeWithUnits().toQSizeF();
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pageheight" ), s.height(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pagewidth" ), s.width(), true ) );
}
else
{
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pageheight" ), QVariant(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pagewidth" ), QVariant(), true ) );
}
}

return scope;
}
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsexpressioncontext.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class QgsSymbol;
class QgsProcessingAlgorithm;
class QgsProcessingContext;
class QgsLayoutAtlas;
class QgsLayoutItem;

/**
* \ingroup core
Expand Down Expand Up @@ -943,6 +944,13 @@ class CORE_EXPORT QgsExpressionContextUtils
*/
static QgsExpressionContextScope *composerItemScope( const QgsComposerItem *composerItem ) SIP_FACTORY;

/**
* Creates a new scope which contains variables and functions relating to a QgsLayoutItem.
* For instance, item size and position.
* \since QGIS 3.0
*/
static QgsExpressionContextScope *layoutItemScope( const QgsLayoutItem *item ) SIP_FACTORY;

/**
* Sets a composer item context variable. This variable will be contained within scopes retrieved via
* composerItemScope().
Expand Down
36 changes: 36 additions & 0 deletions tests/src/core/testqgslayoutitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class TestQgsLayoutItem: public QObject
void excludeFromExports();
void setSceneRect();
void page();
void itemVariablesFunction();

private:

Expand Down Expand Up @@ -1386,6 +1387,41 @@ void TestQgsLayoutItem::page()
QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 5, 38, QgsUnitTypes::LayoutCentimeters ) );
}

void TestQgsLayoutItem::itemVariablesFunction()
{
QgsRectangle extent( 2000, 2800, 2500, 2900 );
QgsLayout l( QgsProject::instance() );

QgsExpression e( QStringLiteral( "map_get( item_variables( 'map_id' ), 'map_scale' )" ) );
// no map
QgsExpressionContext c = l.createExpressionContext();
QVariant r = e.evaluate( &c );
QVERIFY( !r.isValid() );

QgsLayoutItemMap *map = new QgsLayoutItemMap( &l );
map->setExtent( extent );
map->attemptSetSceneRect( QRectF( 30, 60, 200, 100 ) );
map->setCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ) );
l.addLayoutItem( map );
map->setId( QStringLiteral( "map_id" ) );

c = l.createExpressionContext();
r = e.evaluate( &c );
QGSCOMPARENEAR( r.toDouble(), 1.38916e+08, 100 );

QgsExpression e2( QStringLiteral( "map_get( item_variables( 'map_id' ), 'map_crs' )" ) );
r = e2.evaluate( &c );
QCOMPARE( r.toString(), QString( "EPSG:4326" ) );

QgsExpression e3( QStringLiteral( "map_get( item_variables( 'map_id' ), 'map_crs_definition' )" ) );
r = e3.evaluate( &c );
QCOMPARE( r.toString(), QString( "+proj=longlat +datum=WGS84 +no_defs" ) );

QgsExpression e4( QStringLiteral( "map_get( item_variables( 'map_id' ), 'map_units' )" ) );
r = e4.evaluate( &c );
QCOMPARE( r.toString(), QString( "degrees" ) );
}

void TestQgsLayoutItem::rotation()
{
QgsProject proj;
Expand Down
Loading

0 comments on commit 1b93231

Please sign in to comment.