QGIS API Documentation 4.3.0-Master (c8cdfe19d7b)
Loading...
Searching...
No Matches
qgsqmlwidgetwrapper.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsqmlwidgetwrapper.cpp
3
4 ---------------------
5 begin : 25.6.2018
6 copyright : (C) 2018 by Matthias Kuhn
7 email : matthias@opengis.ch
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17#include "qgsqmlwidgetwrapper.h"
18
20#include "qgsmessagelog.h"
22
23#include <QDir>
24#include <QQmlContext>
25#include <QQmlEngine>
26#include <QString>
27#include <QUrl>
28
29#include "moc_qgsqmlwidgetwrapper.cpp"
30
31using namespace Qt::StringLiterals;
32
34 : QgsWidgetWrapper( layer, editor, parent )
35{
36 mQmlFile.setFileTemplate( u"%1%2QgsQmlWidgetWrapper-XXXXXX.qml"_s.arg( QDir::tempPath(), QDir::separator() ) );
37 connect( this, &QgsWidgetWrapper::contextChanged, this, &QgsQmlWidgetWrapper::setQmlContext );
38}
39
41{
42 return true;
43}
44
45QWidget *QgsQmlWidgetWrapper::createWidget( QWidget *parent )
46{
47 QgsAttributeForm *form = qobject_cast<QgsAttributeForm *>( parent );
48
49 if ( form )
50 {
51 mFormFeature = form->feature();
52 connect( form, &QgsAttributeForm::widgetValueChanged, this, [this]( const QString &attribute, const QVariant &newValue, bool attributeChanged ) {
53 if ( attributeChanged )
54 {
55 if ( mRequiresFormScope )
56 {
57 mFormFeature.setAttribute( attribute, newValue );
58 setQmlContext();
59 }
60 }
61 } );
62 }
63
64 QQuickWidget *widget = new QQuickWidget( parent );
65
66 return widget;
67}
68
69void QgsQmlWidgetWrapper::initWidget( QWidget *editor )
70{
71 mWidget = qobject_cast<QQuickWidget *>( editor );
72
73 if ( !mWidget )
74 {
75 return;
76 }
77
78 if ( !mQmlFile.open() )
79 {
80 QgsMessageLog::logMessage( tr( "Failed to open temporary QML file" ) );
81 return;
82 }
83
84 if ( mWidgetBridge )
85 {
86 mWidgetBridge->deleteLater();
87 mWidgetBridge = nullptr;
88 }
89 setQmlContext();
90
91 QUrl sourceUrl = QUrl::fromLocalFile( mQmlFile.fileName() );
92 sourceUrl.setQuery( u"t=%1"_s.arg( QDateTime::currentSecsSinceEpoch() ) );
93 mWidget->setSource( sourceUrl );
94
95 mQmlFile.close();
96}
97
98
100{
101 if ( !mWidget )
102 {
103 return;
104 }
105
106 mWidget->engine()->clearComponentCache();
107
108 initWidget( mWidget );
109}
110
111void QgsQmlWidgetWrapper::setQmlCode( const QString &qmlCode )
112{
113 if ( mQmlCode == qmlCode )
114 {
115 return;
116 }
117
118 mQmlCode = qmlCode;
119
120 bool ok = false;
121 const thread_local QRegularExpression
122 expRe( QStringLiteral( R"re(expression.evaluate\s*\‍(\s*"(.*)"\s*\))re" ), QRegularExpression::PatternOption::MultilineOption | QRegularExpression::PatternOption::DotMatchesEverythingOption | QRegularExpression::InvertedGreedinessOption );
123 QRegularExpressionMatchIterator matchIt = expRe.globalMatch( mQmlCode );
124 while ( !ok && matchIt.hasNext() )
125 {
126 const QRegularExpressionMatch match = matchIt.next();
127 const QgsExpression exp = match.captured( 1 );
129 }
130 mRequiresFormScope = ok;
131
132 if ( !mQmlFile.open() )
133 {
134 QgsMessageLog::logMessage( tr( "Failed to open temporary QML file" ) );
135 return;
136 }
137
138 mQmlFile.resize( 0 );
139 mQmlFile.write( mQmlCode.toUtf8() );
140 mQmlFile.close();
141}
142
143void QgsQmlWidgetWrapper::setQmlContext()
144{
145 if ( !mWidget )
146 {
147 return;
148 }
149
150 const QgsAttributeEditorContext attributeEditorContext = context();
151
152 if ( !mWidgetBridge )
153 {
154 QgsAttributeForm *form = qobject_cast<QgsAttributeForm *>( parent() );
155 mWidgetBridge = new QgsQmlAttributeFormBridge( form, this );
156 mWidget->rootContext()->setContextProperty( "attributeFormBridge", mWidgetBridge );
157 }
158 mWidgetBridge->setContext( attributeEditorContext );
159
160 QgsExpressionContext expressionContext = layer()->createExpressionContext();
161 expressionContext << QgsExpressionContextUtils::formScope( mFormFeature, attributeEditorContext.attributeFormModeString() );
162 if ( attributeEditorContext.parentFormFeature().isValid() )
163 {
164 expressionContext << QgsExpressionContextUtils::parentFormScope( attributeEditorContext.parentFormFeature() );
165 }
166 expressionContext.setFeature( mFeature );
167
168 QgsQmlExpression *qmlExpression = new QgsQmlExpression();
169 qmlExpression->setExpressionContext( expressionContext );
170
171 mWidget->rootContext()->setContextProperty( "expression", qmlExpression );
172}
173
175{
176 if ( !mWidget )
177 {
178 return;
179 }
180
181 mFeature = feature;
182 mFormFeature = feature;
183
184 emit mWidgetBridge->currentFormFeatureChanged();
185
186 setQmlContext();
187}
188
190void QgsQmlExpression::setExpressionContext( const QgsExpressionContext &context )
191{
192 mExpressionContext = context;
193}
194
195QVariant QgsQmlExpression::evaluate( const QString &expression ) const
196{
197 QgsExpression exp = QgsExpression( expression );
198 exp.prepare( &mExpressionContext );
199 return exp.evaluate( &mExpressionContext );
200}
201
202
203QgsQmlAttributeFormBridge::QgsQmlAttributeFormBridge( QgsAttributeForm *form, QObject *parent )
204 : QObject( parent )
205 , mForm( form )
206{
207 if ( mForm )
208 {
209 connect( mForm, &QgsAttributeForm::widgetValueChanged, this, [this]( const QString &, const QVariant &, bool attributeChanged ) {
210 if ( attributeChanged )
211 {
212 emit currentFormFeatureChanged();
213 }
214 } );
215
216 connect( mForm, &QgsAttributeForm::editableChanged, this, &QgsQmlAttributeFormBridge::editableChanged );
217 }
218}
219
220bool QgsQmlAttributeFormBridge::editable() const
221{
222 return mForm ? mForm->editable() : false;
223}
224
225QgsAttributeEditorContext QgsQmlAttributeFormBridge::context() const
226{
227 return mContext;
228}
229
230void QgsQmlAttributeFormBridge::setContext( const QgsAttributeEditorContext &context )
231{
232 mContext = context;
233 emit contextChanged();
234}
235
236QgsVectorLayer *QgsQmlAttributeFormBridge::layer() const
237{
238 return mForm ? mForm->layer() : nullptr;
239}
240
241QgsFeature QgsQmlAttributeFormBridge::currentFormFeature() const
242{
243 return mForm ? mForm->currentFormFeature() : QgsFeature();
244}
245
246void QgsQmlAttributeFormBridge::changeGeometry( const QgsGeometry &geometry )
247{
248 if ( mForm )
249 {
250 mForm->changeGeometry( geometry );
251 emit currentFormFeatureChanged();
252 }
253}
254
255void QgsQmlAttributeFormBridge::changeAttribute( const QString &field, const QVariant &value )
256{
257 if ( mForm )
258 {
259 mForm->changeAttribute( field, value );
260 }
261}
262
263QVariant QgsQmlAttributeFormBridge::attribute( const QString &field )
264{
265 return mForm ? mForm->feature().attribute( field ) : QVariant();
266}
Contains context information for attribute editor widgets.
QString attributeFormModeString() const
Returns given attributeFormMode as string.
The attribute form widget for vector layer features.
void editableChanged()
Emitted when the editable state of the attribute form has changed.
void widgetValueChanged(const QString &attribute, const QVariant &value, bool attributeChanged)
Notifies about changes of attributes.
const QgsFeature & feature() const
Returns feature of attribute form.
static std::unique_ptr< QgsExpressionContextScope > formScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current attribute form/table form...
static std::unique_ptr< QgsExpressionContextScope > parentFormScope(const QgsFeature &formFeature=QgsFeature(), const QString &formMode=QString())
Creates a new scope which contains functions and variables from the current parent attribute form/tab...
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
QVariant evaluate()
Evaluate the feature and return the result.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
bool isValid() const
Returns the validity of this feature.
A geometry is the spatial representation of a feature.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
QWidget * createWidget(QWidget *parent) override
This method should create a new widget with the provided parent.
void initWidget(QWidget *editor) override
This method should initialize the editor widget with runtime data.
bool valid() const override
Returns true if the widget has been properly initialized.
void setFeature(const QgsFeature &feature) override
void reinitWidget()
Clears the content and makes new initialization.
void setQmlCode(const QString &qmlCode)
writes the qmlCode into a temporary file
QgsQmlWidgetWrapper(QgsVectorLayer *layer, QWidget *editor, QWidget *parent)
Create a qml widget wrapper.
static bool expressionRequiresFormScope(const QString &expression)
Check if the expression requires a form scope (i.e.
Represents a vector layer which manages a vector based dataset.
QgsExpressionContext createExpressionContext() const final
This method needs to be reimplemented in all classes which implement this interface and return an exp...
QWidget * widget()
Access the widget managed by this wrapper.
void contextChanged()
Signal when QgsAttributeEditorContext mContext changed.
QgsWidgetWrapper(QgsVectorLayer *vl, QWidget *editor=nullptr, QWidget *parent=nullptr)
Create a new widget wrapper.
const QgsAttributeEditorContext & context() const
Returns information about the context in which this widget is shown.
QgsVectorLayer * layer() const
Returns the vector layer associated with the widget.