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
Expand Up @@ -979,6 +979,9 @@ Rotates the item by a specified ``angle`` in degrees clockwise around a specifie
.. seealso:: :py:func:`itemRotation()` .. seealso:: :py:func:`itemRotation()`
%End %End


virtual QgsExpressionContext createExpressionContext() const;


signals: signals:


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



int pageNumber( QgsLayoutItemPage *page ) const; int pageNumber( QgsLayoutItemPage *page ) const;
%Docstring %Docstring
Returns the page number for the specified ``page``, or -1 if the page 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
Expand Up @@ -1055,7 +1055,7 @@ For instance, current page name and number.


static QgsExpressionContextScope *atlasScope( const QgsLayoutAtlas *atlas ) /Factory/; static QgsExpressionContextScope *atlasScope( const QgsLayoutAtlas *atlas ) /Factory/;
%Docstring %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. 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. :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. For instance, item size and position.


:param composerItem: source composer item :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 %End


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


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



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


QgsExpressionContext createExpressionContext() const override;

signals: signals:


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


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

int QgsLayoutPageCollection::pageNumber( QgsLayoutItemPage *page ) const int QgsLayoutPageCollection::pageNumber( QgsLayoutItemPage *page ) const
{ {
return mPages.indexOf( page ); return mPages.indexOf( page );
Expand Down
10 changes: 10 additions & 0 deletions src/core/layout/qgslayoutpagecollection.h
Expand Up @@ -76,6 +76,16 @@ class CORE_EXPORT QgsLayoutPageCollection : public QObject, public QgsLayoutSeri
*/ */
QgsLayoutItemPage *page( int pageNumber ); 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 * Returns the page number for the specified \a page, or -1 if the page
* is not contained in the collection. * is not contained in the collection.
Expand Down
116 changes: 108 additions & 8 deletions src/core/qgsexpressioncontext.cpp
Expand Up @@ -33,6 +33,7 @@
#include "qgsprocessingalgorithm.h" #include "qgsprocessingalgorithm.h"
#include "qgslayoutatlas.h" #include "qgslayoutatlas.h"
#include "qgslayout.h" #include "qgslayout.h"
#include "qgslayoutpagecollection.h"


#include <QSettings> #include <QSettings>
#include <QDir> #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 class GetLayerVisibility : public QgsScopedExpressionFunction
{ {
public: public:
Expand Down Expand Up @@ -1079,16 +1115,34 @@ QgsExpressionContextScope *QgsExpressionContextUtils::layoutScope( const QgsLayo


//add known layout context variables //add known layout context variables
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_name" ), layout->name(), true ) ); 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_numpages" ), layout->pageCollection()->pageCount(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pageheight" ), composition->paperHeight(), true ) ); if ( layout->pageCollection()->pageCount() > 0 )
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_pagewidth" ), composition->paperWidth(), true ) ); {
// 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 ) ); scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_dpi" ), layout->context().dpi(), true ) );
#endif


#if 0 //TODO scope->addFunction( QStringLiteral( "item_variables" ), new GetLayoutItemVariables( layout ) );
scope->addFunction( QStringLiteral( "item_variables" ), new GetComposerItemVariables( composition ) );
#endif 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(); 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_id" ), composerItem->id(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "item_uuid" ), composerItem->uuid(), true ) ); scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "item_uuid" ), composerItem->uuid(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "layout_page" ), composerItem->page(), 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; return scope;
} }
Expand Down
8 changes: 8 additions & 0 deletions src/core/qgsexpressioncontext.h
Expand Up @@ -41,6 +41,7 @@ class QgsSymbol;
class QgsProcessingAlgorithm; class QgsProcessingAlgorithm;
class QgsProcessingContext; class QgsProcessingContext;
class QgsLayoutAtlas; class QgsLayoutAtlas;
class QgsLayoutItem;


/** /**
* \ingroup core * \ingroup core
Expand Down Expand Up @@ -943,6 +944,13 @@ class CORE_EXPORT QgsExpressionContextUtils
*/ */
static QgsExpressionContextScope *composerItemScope( const QgsComposerItem *composerItem ) SIP_FACTORY; 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 * Sets a composer item context variable. This variable will be contained within scopes retrieved via
* composerItemScope(). * composerItemScope().
Expand Down
36 changes: 36 additions & 0 deletions tests/src/core/testqgslayoutitem.cpp
Expand Up @@ -168,6 +168,7 @@ class TestQgsLayoutItem: public QObject
void excludeFromExports(); void excludeFromExports();
void setSceneRect(); void setSceneRect();
void page(); void page();
void itemVariablesFunction();


private: private:


Expand Down Expand Up @@ -1386,6 +1387,41 @@ void TestQgsLayoutItem::page()
QCOMPARE( item->positionWithUnits(), QgsLayoutPoint( 5, 38, QgsUnitTypes::LayoutCentimeters ) ); 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() void TestQgsLayoutItem::rotation()
{ {
QgsProject proj; QgsProject proj;
Expand Down

0 comments on commit 1b93231

Please sign in to comment.