Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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 8835b2d
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 6 deletions.
Expand Up @@ -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:
Expand Down
7 changes: 6 additions & 1 deletion python/gui/auto_generated/qgsattributeform.sip.in
Expand Up @@ -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 *
Expand Down
2 changes: 1 addition & 1 deletion src/core/vector/qgsvectorlayercache.cpp
Expand Up @@ -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
{
Expand Down
9 changes: 8 additions & 1 deletion src/gui/attributetable/qgsdualview.cpp
Expand Up @@ -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 );
Expand Down
50 changes: 48 additions & 2 deletions src/gui/editorwidgets/qgshtmlwidgetwrapper.cpp
Expand Up @@ -56,8 +56,9 @@ void QgsHtmlWidgetWrapper::initWidget( QWidget *editor )
connect( page, &QWebPage::loadFinished, this, [ = ]( bool ) { fixHeight(); }, Qt::ConnectionType::UniqueConnection );

#endif
}

checkGeometryNeeds();
}

void QgsHtmlWidgetWrapper::reinitWidget( )
{
Expand All @@ -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( )
Expand All @@ -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, [ = ]
{
Expand Down Expand Up @@ -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 )
{
Expand All @@ -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
41 changes: 41 additions & 0 deletions src/gui/editorwidgets/qgshtmlwidgetwrapper.h
Expand Up @@ -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;

Expand All @@ -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;
};


Expand Down Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions src/gui/qgsattributeform.cpp
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down
8 changes: 7 additions & 1 deletion src/gui/qgsattributeform.h
Expand Up @@ -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 );
Expand Down Expand Up @@ -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

1 change: 1 addition & 0 deletions tests/src/gui/CMakeLists.txt
Expand Up @@ -36,6 +36,7 @@ set(TESTS
testqgsfieldexpressionwidget.cpp
testqgsfilewidget.cpp
testqgsfocuswatcher.cpp
testqgshtmlwidgetwrapper.cpp
testqgsmapcanvas.cpp
testqgsmessagebar.cpp
testprojectionissues.cpp
Expand Down
48 changes: 48 additions & 0 deletions tests/src/gui/testqgsdualview.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -57,6 +58,9 @@ class TestQgsDualView : public QObject
void testAttributeFormSharedValueScanning();
void testNoGeom();

void testHtmlWidget_data();
void testHtmlWidget();

private:
QgsMapCanvas *mCanvas = nullptr;
QgsVectorLayer *mPointsLayer = nullptr;
Expand Down Expand Up @@ -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.