Skip to content
Permalink
Browse files

Ensure context is available to builders from data defined buttons

  • Loading branch information
nyalldawson committed Aug 22, 2015
1 parent bfc8f56 commit a7d8519c7f47498dc2589a580a1cd9163d87fac1
@@ -400,7 +400,7 @@ class QgsExpressionContextUtils
/** Creates a new scope which contains variables and functions relating to a QgsMapLayer.
* For instance, layer name, id and fields.
*/
static QgsExpressionContextScope* layerScope( QgsMapLayer* layer ) /Factory/;
static QgsExpressionContextScope* layerScope( const QgsMapLayer* layer ) /Factory/;

/** Helper function for creating an expression context which contains just a feature and fields
* collection. Generally this method should not be used as the created context does not include
@@ -168,6 +168,18 @@ class QgsDataDefinedButton : QToolButton
*/
void clearCheckedWidgets();

//! Callback function for retrieving the expression context for the button
//typedef QgsExpressionContext( *ExpressionContextCallback )( const void* context );

/** Register callback function for retrieving the expression context for the button
* @param fnGetExpressionContext call back function, will be called when the data defined
* button requires the current expression context
* @param context context for callback function
* @note added in QGIS 2.12
* @note not available in Python bindings
*/
//void registerGetExpressionContextCallback( ExpressionContextCallback fnGetExpressionContext, const void* context );

/**
* Sets an assistant used to define the data defined object properties.
* Ownership of the assistant is transferred to the widget.
@@ -482,13 +482,27 @@ QgsComposerItem::DataDefinedProperty QgsComposerHtmlWidget::ddPropertyForWidget(
return QgsComposerItem::NoProperty;
}

static QgsExpressionContext _getExpressionContext( const void* context )
{
const QgsComposerObject* composerObject = ( const QgsComposerObject* ) context;
if ( !composerObject )
{
return QgsExpressionContext();
}

QScopedPointer< QgsExpressionContext > expContext( composerObject->createExpressionContext() );
return QgsExpressionContext( *expContext );
}

void QgsComposerHtmlWidget::populateDataDefinedButtons()
{
QgsVectorLayer* vl = atlasCoverageLayer();

//block signals from data defined buttons
mUrlDDBtn->blockSignals( true );

mUrlDDBtn->registerGetExpressionContextCallback( &_getExpressionContext, mHtml );

//initialise buttons to use atlas coverage layer
mUrlDDBtn->init( vl, mHtml->dataDefinedProperty( QgsComposerItem::SourceUrl ),
QgsDataDefinedButton::AnyType, tr( "url string" ) );
@@ -550,19 +550,27 @@ void QgsComposerItemWidget::setValuesForGuiNonPositionElements()
mExcludeFromPrintsCheckBox->blockSignals( false );
}

static QgsExpressionContext _getExpressionContext( const void* context )
{
const QgsComposerObject* composerObject = ( const QgsComposerObject* ) context;
if ( !composerObject )
{
return QgsExpressionContext();
}

QScopedPointer< QgsExpressionContext > expContext( composerObject->createExpressionContext() );
return QgsExpressionContext( *expContext );
}

void QgsComposerItemWidget::populateDataDefinedButtons()
{
QgsVectorLayer* vl = atlasCoverageLayer();

//block signals from data defined buttons
mXPositionDDBtn->blockSignals( true );
mYPositionDDBtn->blockSignals( true );
mWidthDDBtn->blockSignals( true );
mHeightDDBtn->blockSignals( true );
mItemRotationDDBtn->blockSignals( true );
mTransparencyDDBtn->blockSignals( true );
mBlendModeDDBtn->blockSignals( true );
mExcludePrintsDDBtn->blockSignals( true );
Q_FOREACH ( QgsDataDefinedButton* button, findChildren< QgsDataDefinedButton* >() )
{
button->blockSignals( true );
button->registerGetExpressionContextCallback( &_getExpressionContext, mItem );
}

//initialise buttons to use atlas coverage layer
mXPositionDDBtn->init( vl, mItem->dataDefinedProperty( QgsComposerObject::PositionX ),
@@ -583,14 +591,10 @@ void QgsComposerItemWidget::populateDataDefinedButtons()
QgsDataDefinedButton::String, QgsDataDefinedButton::boolDesc() );

//unblock signals from data defined buttons
mXPositionDDBtn->blockSignals( false );
mYPositionDDBtn->blockSignals( false );
mWidthDDBtn->blockSignals( false );
mHeightDDBtn->blockSignals( false );
mItemRotationDDBtn->blockSignals( false );
mTransparencyDDBtn->blockSignals( false );
mBlendModeDDBtn->blockSignals( false );
mExcludePrintsDDBtn->blockSignals( false );
Q_FOREACH ( QgsDataDefinedButton* button, findChildren< QgsDataDefinedButton* >() )
{
button->blockSignals( false );
}
}

QgsComposerObject::DataDefinedProperty QgsComposerItemWidget::ddPropertyForWidget( QgsDataDefinedButton* widget )
@@ -188,20 +188,27 @@ QgsComposerMapWidget::~QgsComposerMapWidget()
{
}

static QgsExpressionContext _getExpressionContext( const void* context )
{
const QgsComposerObject* composerObject = ( const QgsComposerObject* ) context;
if ( !composerObject )
{
return QgsExpressionContext();
}

QScopedPointer< QgsExpressionContext > expContext( composerObject->createExpressionContext() );
return QgsExpressionContext( *expContext );
}

void QgsComposerMapWidget::populateDataDefinedButtons()
{
QgsVectorLayer* vl = atlasCoverageLayer();

//block signals from data defined buttons
mScaleDDBtn->blockSignals( true );
mMapRotationDDBtn->blockSignals( true );
mXMinDDBtn->blockSignals( true );
mYMinDDBtn->blockSignals( true );
mXMaxDDBtn->blockSignals( true );
mYMaxDDBtn->blockSignals( true );
mAtlasMarginDDBtn->blockSignals( true );
mStylePresetsDDBtn->blockSignals( true );
mLayersDDBtn->blockSignals( true );
Q_FOREACH ( QgsDataDefinedButton* button, findChildren< QgsDataDefinedButton* >() )
{
button->blockSignals( true );
button->registerGetExpressionContextCallback( &_getExpressionContext, mComposerMap );
}

//initialise buttons to use atlas coverage layer
mScaleDDBtn->init( vl, mComposerMap->dataDefinedProperty( QgsComposerObject::MapScale ),
@@ -223,16 +230,10 @@ void QgsComposerMapWidget::populateDataDefinedButtons()
mLayersDDBtn->init( vl, mComposerMap->dataDefinedProperty( QgsComposerObject::MapLayers ),
QgsDataDefinedButton::String, tr( "list of map layer names separated by | characters" ) );

//unblock signals from data defined buttons
mScaleDDBtn->blockSignals( false );
mMapRotationDDBtn->blockSignals( false );
mXMinDDBtn->blockSignals( false );
mYMinDDBtn->blockSignals( false );
mXMaxDDBtn->blockSignals( false );
mYMaxDDBtn->blockSignals( false );
mAtlasMarginDDBtn->blockSignals( false );
mStylePresetsDDBtn->blockSignals( false );
mLayersDDBtn->blockSignals( false );
Q_FOREACH ( QgsDataDefinedButton* button, findChildren< QgsDataDefinedButton* >() )
{
button->blockSignals( false );
}
}

QgsComposerObject::DataDefinedProperty QgsComposerMapWidget::ddPropertyForWidget( QgsDataDefinedButton* widget )
@@ -609,13 +609,27 @@ QgsComposerObject::DataDefinedProperty QgsComposerPictureWidget::ddPropertyForWi
return QgsComposerObject::NoProperty;
}

static QgsExpressionContext _getExpressionContext( const void* context )
{
const QgsComposerObject* composerObject = ( const QgsComposerObject* ) context;
if ( !composerObject )
{
return QgsExpressionContext();
}

QScopedPointer< QgsExpressionContext > expContext( composerObject->createExpressionContext() );
return QgsExpressionContext( *expContext );
}

void QgsComposerPictureWidget::populateDataDefinedButtons()
{
QgsVectorLayer* vl = atlasCoverageLayer();

//block signals from data defined buttons
mSourceDDBtn->blockSignals( true );

mSourceDDBtn->registerGetExpressionContextCallback( &_getExpressionContext, mPicture );

//initialise buttons to use atlas coverage layer
mSourceDDBtn->init( vl, mPicture->dataDefinedProperty( QgsComposerObject::PictureSource ),
QgsDataDefinedButton::AnyType, QgsDataDefinedButton::anyStringDesc() );
@@ -137,6 +137,18 @@ QgsCompositionWidget::~QgsCompositionWidget()

}

static QgsExpressionContext _getExpressionContext( const void* context )
{
const QgsComposition* composition = ( const QgsComposition* ) context;
if ( !composition )
{
return QgsExpressionContext();
}

QScopedPointer< QgsExpressionContext > expContext( composition->createExpressionContext() );
return QgsExpressionContext( *expContext );
}

void QgsCompositionWidget::populateDataDefinedButtons()
{
if ( !mComposition )
@@ -152,11 +164,11 @@ void QgsCompositionWidget::populateDataDefinedButtons()
vl = atlas->coverageLayer();
}

mPaperSizeDDBtn->blockSignals( true );
mPaperWidthDDBtn->blockSignals( true );
mPaperHeightDDBtn->blockSignals( true );
mNumPagesDDBtn->blockSignals( true );
mPaperOrientationDDBtn->blockSignals( true );
Q_FOREACH ( QgsDataDefinedButton* button, findChildren< QgsDataDefinedButton* >() )
{
button->blockSignals( true );
button->registerGetExpressionContextCallback( &_getExpressionContext, mComposition );
}

mPaperSizeDDBtn->init( vl, mComposition->dataDefinedProperty( QgsComposerObject::PresetPaperSize ),
QgsDataDefinedButton::String, QgsDataDefinedButton::paperSizeDesc() );
@@ -172,11 +184,10 @@ void QgsCompositionWidget::populateDataDefinedButtons()
//initial state of controls - disable related controls when dd buttons are active
mPaperSizeComboBox->setEnabled( !mPaperSizeDDBtn->isActive() );

mPaperSizeDDBtn->blockSignals( false );
mPaperWidthDDBtn->blockSignals( false );
mPaperHeightDDBtn->blockSignals( false );
mNumPagesDDBtn->blockSignals( false );
mPaperOrientationDDBtn->blockSignals( false );
Q_FOREACH ( QgsDataDefinedButton* button, findChildren< QgsDataDefinedButton* >() )
{
button->blockSignals( false );
}
}

void QgsCompositionWidget::variablesChanged()
@@ -872,14 +872,33 @@ void QgsLabelingGui::setDataDefinedProperty( const QgsDataDefinedButton* ddBtn,
lyr.setDataDefinedProperty( p, map.value( "active" ).toInt(), map.value( "useexpr" ).toInt(), map.value( "expression" ), map.value( "field" ) );
}

static QgsExpressionContext _getExpressionContext( const void* context )
{
QgsExpressionContext expContext;
expContext << QgsExpressionContextUtils::globalScope()
<< QgsExpressionContextUtils::projectScope();

const QgsVectorLayer* layer = ( const QgsVectorLayer* ) context;
if ( layer )
expContext << QgsExpressionContextUtils::layerScope( layer );

return expContext;
}

void QgsLabelingGui::populateDataDefinedButtons( QgsPalLayerSettings& s )
{
Q_FOREACH ( QgsDataDefinedButton* button, findChildren< QgsDataDefinedButton* >() )
{
button->registerGetExpressionContextCallback( &_getExpressionContext, mLayer );
}

// don't register enable/disable siblings, since visual feedback from data defined buttons should be enough,
// and ability to edit layer-level setting should remain enabled regardless

QString trString = tr( "string " );

// text style

mFontDDBtn->init( mLayer, s.dataDefinedProperty( QgsPalLayerSettings::Family ),
QgsDataDefinedButton::String,
trString + tr( "[<b>family</b>|<b>family[foundry]</b>],<br>"
@@ -527,7 +527,7 @@ void QgsExpressionContextUtils::setProjectVariables( const QgsStringMap &variabl
project->writeEntry( "Variables", "/variableValues", variableValues );
}

QgsExpressionContextScope* QgsExpressionContextUtils::layerScope( QgsMapLayer* layer )
QgsExpressionContextScope* QgsExpressionContextUtils::layerScope( const QgsMapLayer* layer )
{
QgsExpressionContextScope* scope = new QgsExpressionContextScope( QObject::tr( "Layer" ) );

@@ -566,11 +566,13 @@ QgsExpressionContextScope* QgsExpressionContextUtils::layerScope( QgsMapLayer* l
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_crs", layer->crs().authid(), true ) );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_crsdefinition", layer->crs().toProj4(), true ) );

QgsGeometry* extentGeom = QgsGeometry::fromRect( layer->extent() );
//some methods we want aren't const
QgsMapLayer* nonConstLayer = const_cast< QgsMapLayer* >( layer );
QgsGeometry* extentGeom = QgsGeometry::fromRect( nonConstLayer->extent() );
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_extent", QVariant::fromValue( *extentGeom ), true ) );
delete extentGeom;

QgsVectorLayer* vLayer = dynamic_cast< QgsVectorLayer* >( layer );
QgsVectorLayer* vLayer = dynamic_cast< QgsVectorLayer* >( nonConstLayer );
if ( vLayer )
{
scope->addVariable( QgsExpressionContextScope::StaticVariable( "layer_geometrytype", vLayer->type(), true ) );
@@ -435,7 +435,7 @@ class CORE_EXPORT QgsExpressionContextUtils
/** Creates a new scope which contains variables and functions relating to a QgsMapLayer.
* For instance, layer name, id and fields.
*/
static QgsExpressionContextScope* layerScope( QgsMapLayer* layer );
static QgsExpressionContextScope* layerScope( const QgsMapLayer *layer );

/** Sets a layer context variable. This variable will be contained within scopes retrieved via
* layerScope().
@@ -41,6 +41,8 @@ QgsDataDefinedButton::QgsDataDefinedButton( QWidget* parent,
DataTypes datatypes,
QString description )
: QToolButton( parent )
, mExpressionContextCallback( 0 )
, mExpressionContextCallbackContext( 0 )
{
// set up static icons
if ( mIconDataDefine.isNull() )
@@ -452,7 +454,9 @@ void QgsDataDefinedButton::showAssistant()

void QgsDataDefinedButton::showExpressionDialog()
{
QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer*>( mVectorLayer ), getExpression() );
QgsExpressionContext context = mExpressionContextCallback ? mExpressionContextCallback( mExpressionContextCallbackContext ) : QgsExpressionContext();

QgsExpressionBuilderDialog d( const_cast<QgsVectorLayer*>( mVectorLayer ), getExpression(), this, "generic", context );
if ( d.exec() == QDialog::Accepted )
{
QString newExp = d.expressionText();
@@ -632,6 +636,12 @@ QList<QWidget*> QgsDataDefinedButton::registeredCheckedWidgets()
return wdgtList;
}

void QgsDataDefinedButton::registerGetExpressionContextCallback( QgsDataDefinedButton::ExpressionContextCallback fnGetExpressionContext, const void *context )
{
mExpressionContextCallback = fnGetExpressionContext;
mExpressionContextCallbackContext = context;
}

void QgsDataDefinedButton::setAssistant( const QString& title, QgsDataDefinedAssistant *assistant )
{
mActionAssistant->setText( title.isEmpty() ? tr( "Assistant..." ) : title );
@@ -21,6 +21,7 @@
#include <QPointer>
#include <QToolButton>
#include <QScopedPointer>
#include "qgsexpressioncontext.h"

class QgsVectorLayer;
class QgsDataDefined;
@@ -194,6 +195,18 @@ class GUI_EXPORT QgsDataDefinedButton: public QToolButton
*/
void clearCheckedWidgets() { mCheckedWidgets.clear(); }

//! Callback function for retrieving the expression context for the button
typedef QgsExpressionContext( *ExpressionContextCallback )( const void* context );

/** Register callback function for retrieving the expression context for the button
* @param fnGetExpressionContext call back function, will be called when the data defined
* button requires the current expression context
* @param context context for callback function
* @note added in QGIS 2.12
* @note not available in Python bindings
*/
void registerGetExpressionContextCallback( ExpressionContextCallback fnGetExpressionContext, const void* context );

/**
* Sets an assistant used to define the data defined object properties.
* Ownership of the assistant is transferred to the widget.
@@ -332,6 +345,9 @@ class GUI_EXPORT QgsDataDefinedButton: public QToolButton
static QIcon mIconDataDefineExpressionOn;
static QIcon mIconDataDefineExpressionError;

ExpressionContextCallback mExpressionContextCallback;
const void* mExpressionContextCallbackContext;

private slots:
void aboutToShowMenu();
void menuActionTriggered( QAction* action );

5 comments on commit a7d8519

@m-kuhn

This comment has been minimized.

Copy link
Member

@m-kuhn m-kuhn replied Dec 10, 2015

@nyalldawson I have trouble understanding why the callback is required. Does the context change through the lifetime of these buttons?

I wonder because in the geometry generator config widget there's an expression widget which does not have the same "on demand popup" semantics like the button and it would benefit from a setter approach.

@nyalldawson

This comment has been minimized.

Copy link
Collaborator Author

@nyalldawson nyalldawson replied Dec 10, 2015

@m-kuhn

Does the context change through the lifetime of these buttons?

yes, it can do. An example would be adding/changing a layer variable from the layer properties window.

I'm not sure what the best approach would be for an embedded expression widget. I can't think of a signal based approach that would work cleanly (ideas welcome!).

Best I can think of would be if the function tree was updated when the widget is focused, but that seems very hacky....

@m-kuhn

This comment has been minimized.

Copy link
Member

@m-kuhn m-kuhn replied Dec 10, 2015

Just thinking out loud:

Let's say each scope has a signal expressionContextChanged (app?, project, layer, symbol...)

Then the layer does connect( project, SIGNAL(expressionContextChanged), this, SIGNAL(expressionContextChanged)), the symbol does connect( layer, SIGNAL(expressionContextChanged), this, SIGNAL(expressionContextChanged))

The layer can add additional ones like connect( this, SIGNAL(fieldsChanged), this, SIGNAL(expressionContextChanged))

Each scope can be cached on the appropriate level (already done?) on first request after being marked dirty by the appropriate signal.

@nyalldawson

This comment has been minimized.

Copy link
Collaborator Author

@nyalldawson nyalldawson replied Dec 10, 2015

I was trying to avoid the expense of making scopes/contexts a QObject, if at all possible...

@m-kuhn

This comment has been minimized.

Copy link
Member

@m-kuhn m-kuhn replied Dec 10, 2015

Yeah, but as far as I can see, there's always an equivalent to a scope which is a QObject. QgsProject, QgsVectorLayer...

Please sign in to comment.
You can’t perform that action at this time.