Skip to content
Permalink
Browse files
Merge pull request #43617 from troopa81/fix_htmlwidget_geom
Set up cache geometry if HTML widget needs it
  • Loading branch information
elpaso committed Jun 30, 2021
2 parents 791f64d + ac03137 commit 8835b2d05e1e7fb085f0a643ff2c9bdbca11e27b
@@ -46,6 +46,13 @@ Clears the content and makes new initialization
void setHtmlCode( const QString &htmlCode );
%Docstring
Sets the HTML code to ``htmlCode``
%End

bool needsGeometry() const;
%Docstring
Returns true if the widget needs feature geometry

.. versionadded:: 3.20
%End

public slots:
@@ -319,9 +319,14 @@ have filter expressions that depend on the parent form scope.
.. versionadded:: 3.14
%End

bool needsGeometry() const;
%Docstring
Returns ``True`` if any of the form widgets need feature geometry

};
.. versionadded:: 3.20
%End

};

/************************************************************************
* This file has been generated automatically from *
@@ -69,7 +69,7 @@ void QgsVectorLayerCache::setCacheGeometry( bool cacheGeometry )
mCacheGeometry = shouldCacheGeometry;
if ( cacheGeometry )
{
connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayerCache::geometryChanged );
connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayerCache::geometryChanged, Qt::UniqueConnection );
}
else
{
@@ -130,7 +130,14 @@ void QgsDualView::init( QgsVectorLayer *layer, QgsMapCanvas *mapCanvas, const Qg
mLayer = layer;
mEditorContext = context;

initLayerCache( !( request.flags() & QgsFeatureRequest::NoGeometry ) || !request.filterRect().isNull() );
// create an empty form to find out if it needs geometry or not
QgsAttributeForm emptyForm( mLayer, QgsFeature(), mEditorContext );

const bool needsGeometry = !( request.flags() & QgsFeatureRequest::NoGeometry )
|| !request.filterRect().isNull()
|| emptyForm.needsGeometry();

initLayerCache( needsGeometry );
initModels( mapCanvas, request, loadFeatures );

mConditionalFormatWidget->setLayer( mLayer );
@@ -56,8 +56,9 @@ void QgsHtmlWidgetWrapper::initWidget( QWidget *editor )
connect( page, &QWebPage::loadFinished, this, [ = ]( bool ) { fixHeight(); }, Qt::ConnectionType::UniqueConnection );

#endif
}

checkGeometryNeeds();
}

void QgsHtmlWidgetWrapper::reinitWidget( )
{
@@ -67,9 +68,35 @@ void QgsHtmlWidgetWrapper::reinitWidget( )
initWidget( mWidget );
}

void QgsHtmlWidgetWrapper::checkGeometryNeeds()
{
if ( !mWidget )
return;

// initialize a temporary QgsWebView to render HTML code and check if one evaluated expression
// needs geometry
QgsWebView webView;
NeedsGeometryEvaluator evaluator;

const QgsAttributeEditorContext attributecontext = context();
QgsExpressionContext expressionContext = layer()->createExpressionContext();
evaluator.setExpressionContext( expressionContext );

auto frame = webView.page()->mainFrame();
connect( frame, &QWebFrame::javaScriptWindowObjectCleared, frame, [ frame, &evaluator ]
{
frame->addToJavaScriptWindowObject( QStringLiteral( "expression" ), &evaluator );
} );

webView.setHtml( mHtmlCode );

mNeedsGeometry = evaluator.needsGeometry();
}

void QgsHtmlWidgetWrapper::setHtmlCode( const QString &htmlCode )
{
mHtmlCode = htmlCode;
checkGeometryNeeds();
}

void QgsHtmlWidgetWrapper::setHtmlContext( )
@@ -88,7 +115,6 @@ void QgsHtmlWidgetWrapper::setHtmlContext( )

HtmlExpression *htmlExpression = new HtmlExpression();
htmlExpression->setExpressionContext( expressionContext );
mWidget->page()->settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
auto frame = mWidget->page()->mainFrame();
connect( frame, &QWebFrame::javaScriptWindowObjectCleared, frame, [ = ]
{
@@ -116,6 +142,12 @@ void QgsHtmlWidgetWrapper::setFeature( const QgsFeature &feature )
setHtmlContext();
}

bool QgsHtmlWidgetWrapper::needsGeometry() const
{
return mNeedsGeometry;
}


///@cond PRIVATE
void HtmlExpression::setExpressionContext( const QgsExpressionContext &context )
{
@@ -128,4 +160,18 @@ QString HtmlExpression::evaluate( const QString &expression ) const
exp.prepare( &mExpressionContext );
return exp.evaluate( &mExpressionContext ).toString();
}

void NeedsGeometryEvaluator::evaluate( const QString &expression )
{
QgsExpression exp = QgsExpression( expression );
exp.prepare( &mExpressionContext );
mNeedsGeometry |= exp.needsGeometry();
}

void NeedsGeometryEvaluator::setExpressionContext( const QgsExpressionContext &context )
{
mExpressionContext = context;
}


///@endcond
@@ -53,6 +53,12 @@ class GUI_EXPORT QgsHtmlWidgetWrapper : public QgsWidgetWrapper
//! Sets the HTML code to \a htmlCode
void setHtmlCode( const QString &htmlCode );

/**
* Returns true if the widget needs feature geometry
* \since QGIS 3.20
*/
bool needsGeometry() const;

public slots:
void setFeature( const QgsFeature &feature ) override;

@@ -64,9 +70,16 @@ class GUI_EXPORT QgsHtmlWidgetWrapper : public QgsWidgetWrapper
#endif

private:

//! checks if HTML contains geometry related expression
void checkGeometryNeeds();

QString mHtmlCode;
QgsWebView *mWidget = nullptr;
QgsFeature mFeature;
bool mNeedsGeometry = false;

friend class TestQgsHtmlWidgetWrapper;
};


@@ -94,6 +107,34 @@ class HtmlExpression : public QObject
private:
QgsExpressionContext mExpressionContext;
};

/**
* \ingroup gui
* Evaluate expression when rendering the html content to determine whether we need feature
* geometry or not for later evaluation
* \since QGIS 3.20
*/
class NeedsGeometryEvaluator : public QObject
{
Q_OBJECT

public:

//! Returns true if the widget needs feature geometry
bool needsGeometry() const { return mNeedsGeometry; }

//! set context use when evaluating expression
void setExpressionContext( const QgsExpressionContext &context );

//! evaluates the value regarding the \a expression and the context
Q_INVOKABLE void evaluate( const QString &expression );

private:
bool mNeedsGeometry = false;
QgsExpressionContext mExpressionContext;
};


///@endcond
#endif //SIP_RUN

@@ -1332,6 +1332,11 @@ void QgsAttributeForm::parentFormValueChanged( const QString &attribute, const Q
}
}

bool QgsAttributeForm::needsGeometry() const
{
return mNeedsGeometry;
}

void QgsAttributeForm::synchronizeState()
{
bool isEditable = ( mFeature.isValid()
@@ -1396,6 +1401,7 @@ void QgsAttributeForm::init()

// Cleanup of any previously shown widget, we start from scratch
QWidget *formWidget = nullptr;
mNeedsGeometry = false;

bool buttonBoxVisible = true;
// Cleanup button box but preserve visibility
@@ -2234,6 +2240,7 @@ QgsAttributeForm::WidgetInfo QgsAttributeForm::createWidgetFromDef( const QgsAtt
newWidgetInfo.labelText = elementDef->name();
newWidgetInfo.labelOnTop = true;
newWidgetInfo.showLabel = widgetDef->showLabel();
mNeedsGeometry |= htmlWrapper->needsGeometry();
break;
}

@@ -331,6 +331,11 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
*/
void parentFormValueChanged( const QString &attribute, const QVariant &newValue );

/**
* Returns TRUE if any of the form widgets need feature geometry
* \since QGIS 3.20
*/
bool needsGeometry() const;

private slots:
void onAttributeChanged( const QVariant &value, const QVariantList &additionalFieldValues );
@@ -516,9 +521,10 @@ class GUI_EXPORT QgsAttributeForm : public QWidget
//! List of updated fields to avoid recursion on the setting of defaultValues
QList<int> mAlreadyUpdatedFields;

bool mNeedsGeometry = false;

friend class TestQgsDualView;
friend class TestQgsAttributeForm;
};

#endif // QGSATTRIBUTEFORM_H

@@ -36,6 +36,7 @@ set(TESTS
testqgsfieldexpressionwidget.cpp
testqgsfilewidget.cpp
testqgsfocuswatcher.cpp
testqgshtmlwidgetwrapper.cpp
testqgsmapcanvas.cpp
testqgsmessagebar.cpp
testprojectionissues.cpp
@@ -19,6 +19,7 @@
#include <editorwidgets/core/qgseditorwidgetregistry.h>
#include <attributetable/qgsattributetableview.h>
#include <attributetable/qgsdualview.h>
#include <editform/qgsattributeeditorhtmlelement.h>
#include "qgsattributeform.h"
#include <qgsapplication.h>
#include "qgsfeatureiterator.h"
@@ -57,6 +58,9 @@ class TestQgsDualView : public QObject
void testAttributeFormSharedValueScanning();
void testNoGeom();

void testHtmlWidget_data();
void testHtmlWidget();

private:
QgsMapCanvas *mCanvas = nullptr;
QgsVectorLayer *mPointsLayer = nullptr;
@@ -340,5 +344,49 @@ void TestQgsDualView::testNoGeom()
QVERIFY( ( model->request().flags() & QgsFeatureRequest::NoGeometry ) );
}

void TestQgsDualView::testHtmlWidget_data()
{
QTest::addColumn<QString>( "expression" );
QTest::addColumn<bool>( "expectedCacheGeometry" );

QTest::newRow( "with-geometry" ) << "geom_to_wkt($geometry)" << true;
QTest::newRow( "without-geometry" ) << "2+pk" << false;
}

void TestQgsDualView::testHtmlWidget()
{
// check that HTML widget set cache geometry when needed

QFETCH( QString, expression );
QFETCH( bool, expectedCacheGeometry );

QgsVectorLayer layer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int" ), QStringLiteral( "layer" ), QStringLiteral( "memory" ) );
QgsProject::instance()->addMapLayer( &layer, false, false );
QgsFeature f( layer.fields() );
f.setAttribute( QStringLiteral( "pk" ), 1 );
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "POINT(0.5 0.5)" ) ) );
QVERIFY( f.isValid() );
QVERIFY( f.geometry().isGeosValid() );
QVERIFY( layer.dataProvider()->addFeature( f ) );

QgsEditFormConfig editFormConfig = layer.editFormConfig();
editFormConfig.clearTabs();
QgsAttributeEditorHtmlElement *htmlElement = new QgsAttributeEditorHtmlElement( "HtmlWidget", nullptr );
htmlElement->setHtmlCode( QStringLiteral( "The text is '<script>document.write(expression.evaluate(\"%1\"));</script>'" ).arg( expression ) );
editFormConfig.addTab( htmlElement );
editFormConfig.setLayout( QgsEditFormConfig::TabLayout );
layer.setEditFormConfig( editFormConfig );

QgsFeatureRequest request;
request.setFlags( QgsFeatureRequest::NoGeometry );

QgsDualView dualView;
dualView.setView( QgsDualView::AttributeEditor );
dualView.init( &layer, mCanvas, request );
QCOMPARE( dualView.mLayerCache->cacheGeometry(), expectedCacheGeometry );

QgsProject::instance()->removeMapLayer( &layer );
}

QGSTEST_MAIN( TestQgsDualView )
#include "testqgsdualview.moc"

0 comments on commit 8835b2d

Please sign in to comment.