1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qqmlexpression.h" |
5 | #include "qqmlexpression_p.h" |
6 | |
7 | #include "qqmlengine_p.h" |
8 | #include "qqmlscriptstring_p.h" |
9 | #include "qqmlbinding_p.h" |
10 | #include <private/qqmlsourcecoordinate_p.h> |
11 | #include <private/qv4qmlcontext_p.h> |
12 | |
13 | #include <QtCore/qdebug.h> |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | QQmlExpressionPrivate::QQmlExpressionPrivate() |
18 | : QQmlJavaScriptExpression(), |
19 | expressionFunctionValid(true), |
20 | line(0), column(0) |
21 | { |
22 | } |
23 | |
24 | QQmlExpressionPrivate::~QQmlExpressionPrivate() |
25 | { |
26 | } |
27 | |
28 | void QQmlExpressionPrivate::init(const QQmlRefPointer<QQmlContextData> &ctxt, const QString &expr, |
29 | QObject *me) |
30 | { |
31 | expression = expr; |
32 | |
33 | QQmlJavaScriptExpression::setContext(ctxt); |
34 | setScopeObject(me); |
35 | expressionFunctionValid = false; |
36 | } |
37 | |
38 | void QQmlExpressionPrivate::init(const QQmlRefPointer<QQmlContextData> &ctxt, |
39 | QV4::Function *runtimeFunction, QObject *me) |
40 | { |
41 | expressionFunctionValid = true; |
42 | QV4::ExecutionEngine *engine = ctxt->engine()->handle(); |
43 | QV4::Scope scope(engine); |
44 | QV4::Scoped<QV4::QmlContext> qmlContext(scope, QV4::QmlContext::create(parent: engine->rootContext(), context: ctxt, scopeObject: me)); |
45 | setupFunction(qmlContext, f: runtimeFunction); |
46 | |
47 | QQmlJavaScriptExpression::setContext(ctxt); |
48 | setScopeObject(me); |
49 | } |
50 | |
51 | /*! |
52 | \class QQmlExpression |
53 | \since 5.0 |
54 | \inmodule QtQml |
55 | \brief The QQmlExpression class evaluates JavaScript in a QML context. |
56 | |
57 | For example, given a file \c main.qml like this: |
58 | |
59 | \qml |
60 | import QtQuick 2.0 |
61 | |
62 | Item { |
63 | width: 200; height: 200 |
64 | } |
65 | \endqml |
66 | |
67 | The following code evaluates a JavaScript expression in the context of the |
68 | above QML: |
69 | |
70 | \code |
71 | QQmlEngine *engine = new QQmlEngine; |
72 | QQmlComponent component(engine, QUrl::fromLocalFile("main.qml")); |
73 | |
74 | QObject *myObject = component.create(); |
75 | QQmlExpression *expr = new QQmlExpression(engine->rootContext(), myObject, "width * 2"); |
76 | int result = expr->evaluate().toInt(); // result = 400 |
77 | \endcode |
78 | */ |
79 | |
80 | /*! |
81 | Create an invalid QQmlExpression. |
82 | |
83 | As the expression will not have an associated QQmlContext, this will be a |
84 | null expression object and its value will always be an invalid QVariant. |
85 | */ |
86 | QQmlExpression::QQmlExpression() |
87 | : QObject(*new QQmlExpressionPrivate, nullptr) |
88 | { |
89 | } |
90 | |
91 | /*! |
92 | Create a QQmlExpression object that is a child of \a parent. |
93 | |
94 | The \a script provides the expression to be evaluated, the context to evaluate it in, |
95 | and the scope object to evaluate it with. If provided, \a ctxt and \a scope will override |
96 | the context and scope object provided by \a script. |
97 | |
98 | \sa QQmlScriptString |
99 | */ |
100 | QQmlExpression::QQmlExpression(const QQmlScriptString &script, QQmlContext *ctxt, QObject *scope, QObject *parent) |
101 | : QObject(*new QQmlExpressionPrivate, parent) |
102 | { |
103 | Q_D(QQmlExpression); |
104 | if (ctxt && !ctxt->isValid()) |
105 | return; |
106 | |
107 | const QQmlScriptStringPrivate *scriptPrivate = script.d.data(); |
108 | if (!scriptPrivate) { |
109 | // A null QQmlScriptStringPrivate is an empty expression without context. |
110 | // We may still want the explicitly passed context, though. |
111 | if (ctxt) |
112 | d->init(ctxt: QQmlContextData::get(context: ctxt), expr: QString(), me: scope); |
113 | return; |
114 | } |
115 | |
116 | if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) |
117 | return; |
118 | |
119 | QQmlRefPointer<QQmlContextData> evalCtxtData |
120 | = QQmlContextData::get(context: ctxt ? ctxt : scriptPrivate->context); |
121 | QObject *scopeObject = scope ? scope : scriptPrivate->scope; |
122 | QV4::Function *runtimeFunction = nullptr; |
123 | |
124 | if (scriptPrivate->context) { |
125 | QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(context: scriptPrivate->context); |
126 | QQmlEnginePrivate *engine = QQmlEnginePrivate::get(e: scriptPrivate->context->engine()); |
127 | if (engine |
128 | && ctxtdata |
129 | && !ctxtdata->urlString().isEmpty() |
130 | && ctxtdata->typeCompilationUnit()) { |
131 | d->url = ctxtdata->urlString(); |
132 | d->line = scriptPrivate->lineNumber; |
133 | d->column = scriptPrivate->columnNumber; |
134 | |
135 | if (scriptPrivate->bindingId != QQmlBinding::Invalid) |
136 | runtimeFunction = ctxtdata->typeCompilationUnit()->runtimeFunctions.at(i: scriptPrivate->bindingId); |
137 | } |
138 | } |
139 | |
140 | if (runtimeFunction) { |
141 | d->expression = scriptPrivate->script; |
142 | d->init(ctxt: evalCtxtData, runtimeFunction, me: scopeObject); |
143 | } else |
144 | d->init(ctxt: evalCtxtData, expr: scriptPrivate->script, me: scopeObject); |
145 | } |
146 | |
147 | /*! |
148 | Create a QQmlExpression object that is a child of \a parent. |
149 | |
150 | The \a expression JavaScript will be executed in the \a ctxt QQmlContext. |
151 | If specified, the \a scope object's properties will also be in scope during |
152 | the expression's execution. |
153 | */ |
154 | QQmlExpression::QQmlExpression(QQmlContext *ctxt, QObject *scope, const QString &expression, |
155 | QObject *parent) |
156 | : QObject(*new QQmlExpressionPrivate, parent) |
157 | { |
158 | Q_D(QQmlExpression); |
159 | d->init(ctxt: QQmlContextData::get(context: ctxt), expr: expression, me: scope); |
160 | } |
161 | |
162 | /*! |
163 | \internal |
164 | */ |
165 | QQmlExpression::QQmlExpression(QQmlExpressionPrivate &dd, QObject *parent) : QObject(dd, parent) |
166 | { |
167 | // Q_D(QQmlExpression); |
168 | // d->init(QQmlContextData::get(ctxt), expression, scope); |
169 | } |
170 | |
171 | /*! |
172 | Destroy the QQmlExpression instance. |
173 | */ |
174 | QQmlExpression::~QQmlExpression() |
175 | { |
176 | } |
177 | |
178 | /*! |
179 | Returns the QQmlEngine this expression is associated with, or \nullptr if there |
180 | is no association or the QQmlEngine has been destroyed. |
181 | */ |
182 | QQmlEngine *QQmlExpression::engine() const |
183 | { |
184 | Q_D(const QQmlExpression); |
185 | return d->engine(); |
186 | } |
187 | |
188 | /*! |
189 | Returns the QQmlContext this expression is associated with, or \nullptr if there |
190 | is no association or the QQmlContext has been destroyed. |
191 | */ |
192 | QQmlContext *QQmlExpression::context() const |
193 | { |
194 | Q_D(const QQmlExpression); |
195 | return d->publicContext(); |
196 | } |
197 | |
198 | /*! |
199 | Returns the expression string. |
200 | */ |
201 | QString QQmlExpression::expression() const |
202 | { |
203 | Q_D(const QQmlExpression); |
204 | return d->expression; |
205 | } |
206 | |
207 | /*! |
208 | Set the expression to \a expression. |
209 | */ |
210 | void QQmlExpression::setExpression(const QString &expression) |
211 | { |
212 | Q_D(QQmlExpression); |
213 | |
214 | d->resetNotifyOnValueChanged(); |
215 | d->expression = expression; |
216 | d->expressionFunctionValid = false; |
217 | } |
218 | |
219 | // Must be called with a valid handle scope |
220 | QV4::ReturnedValue QQmlExpressionPrivate::v4value(bool *isUndefined) |
221 | { |
222 | if (!expressionFunctionValid) { |
223 | createQmlBinding(ctxt: context(), scope: scopeObject(), code: expression, filename: url, line); |
224 | expressionFunctionValid = true; |
225 | if (hasError()) { |
226 | if (isUndefined) |
227 | *isUndefined = true; |
228 | return QV4::Encode::undefined(); |
229 | } |
230 | } |
231 | |
232 | return evaluate(isUndefined); |
233 | } |
234 | |
235 | QVariant QQmlExpressionPrivate::value(bool *isUndefined) |
236 | { |
237 | Q_Q(QQmlExpression); |
238 | |
239 | if (!hasValidContext()) { |
240 | qWarning(msg: "QQmlExpression: Attempted to evaluate an expression in an invalid context" ); |
241 | return QVariant(); |
242 | } |
243 | |
244 | QQmlEngine *engine = q->engine(); |
245 | QQmlEnginePrivate *ep = QQmlEnginePrivate::get(e: engine); |
246 | QVariant rv; |
247 | |
248 | ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation. |
249 | |
250 | { |
251 | QV4::Scope scope(engine->handle()); |
252 | QV4::ScopedValue result(scope, v4value(isUndefined)); |
253 | if (!hasError()) |
254 | rv = QV4::ExecutionEngine::toVariant(value: result, typeHint: QMetaType {}); |
255 | } |
256 | |
257 | ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete. |
258 | |
259 | return rv; |
260 | } |
261 | |
262 | /*! |
263 | Evaulates the expression, returning the result of the evaluation, |
264 | or an invalid QVariant if the expression is invalid or has an error. |
265 | |
266 | \a valueIsUndefined is set to true if the expression resulted in an |
267 | undefined value. |
268 | |
269 | \sa hasError(), error() |
270 | */ |
271 | QVariant QQmlExpression::evaluate(bool *valueIsUndefined) |
272 | { |
273 | Q_D(QQmlExpression); |
274 | return d->value(isUndefined: valueIsUndefined); |
275 | } |
276 | |
277 | /*! |
278 | Returns true if the valueChanged() signal is emitted when the expression's evaluated |
279 | value changes. |
280 | */ |
281 | bool QQmlExpression::notifyOnValueChanged() const |
282 | { |
283 | Q_D(const QQmlExpression); |
284 | return d->notifyOnValueChanged(); |
285 | } |
286 | |
287 | /*! |
288 | Sets whether the valueChanged() signal is emitted when the |
289 | expression's evaluated value changes. |
290 | |
291 | If \a notifyOnChange is true, the QQmlExpression will |
292 | monitor properties involved in the expression's evaluation, and emit |
293 | QQmlExpression::valueChanged() if they have changed. This |
294 | allows an application to ensure that any value associated with the |
295 | result of the expression remains up to date. |
296 | |
297 | If \a notifyOnChange is false (default), the QQmlExpression |
298 | will not montitor properties involved in the expression's |
299 | evaluation, and QQmlExpression::valueChanged() will never be |
300 | emitted. This is more efficient if an application wants a "one off" |
301 | evaluation of the expression. |
302 | */ |
303 | void QQmlExpression::setNotifyOnValueChanged(bool notifyOnChange) |
304 | { |
305 | Q_D(QQmlExpression); |
306 | d->setNotifyOnValueChanged(notifyOnChange); |
307 | } |
308 | |
309 | /*! |
310 | Returns the source file URL for this expression. The source location must |
311 | have been previously set by calling setSourceLocation(). |
312 | */ |
313 | QString QQmlExpression::sourceFile() const |
314 | { |
315 | Q_D(const QQmlExpression); |
316 | return d->url; |
317 | } |
318 | |
319 | /*! |
320 | Returns the source file line number for this expression. The source location |
321 | must have been previously set by calling setSourceLocation(). |
322 | */ |
323 | int QQmlExpression::lineNumber() const |
324 | { |
325 | Q_D(const QQmlExpression); |
326 | return qmlConvertSourceCoordinate<quint16, int>(n: d->line); |
327 | } |
328 | |
329 | /*! |
330 | Returns the source file column number for this expression. The source location |
331 | must have been previously set by calling setSourceLocation(). |
332 | */ |
333 | int QQmlExpression::columnNumber() const |
334 | { |
335 | Q_D(const QQmlExpression); |
336 | return qmlConvertSourceCoordinate<quint16, int>(n: d->column); |
337 | } |
338 | |
339 | /*! |
340 | Set the location of this expression to \a line and \a column of \a url. This information |
341 | is used by the script engine. |
342 | */ |
343 | void QQmlExpression::setSourceLocation(const QString &url, int line, int column) |
344 | { |
345 | Q_D(QQmlExpression); |
346 | d->url = url; |
347 | d->line = qmlConvertSourceCoordinate<int, quint16>(n: line); |
348 | d->column = qmlConvertSourceCoordinate<int, quint16>(n: column); |
349 | } |
350 | |
351 | /*! |
352 | Returns the expression's scope object, if provided, otherwise 0. |
353 | |
354 | In addition to data provided by the expression's QQmlContext, the scope |
355 | object's properties are also in scope during the expression's evaluation. |
356 | */ |
357 | QObject *QQmlExpression::scopeObject() const |
358 | { |
359 | Q_D(const QQmlExpression); |
360 | return d->scopeObject(); |
361 | } |
362 | |
363 | /*! |
364 | Returns true if the last call to evaluate() resulted in an error, |
365 | otherwise false. |
366 | |
367 | \sa error(), clearError() |
368 | */ |
369 | bool QQmlExpression::hasError() const |
370 | { |
371 | Q_D(const QQmlExpression); |
372 | return d->hasError(); |
373 | } |
374 | |
375 | /*! |
376 | Clear any expression errors. Calls to hasError() following this will |
377 | return false. |
378 | |
379 | \sa hasError(), error() |
380 | */ |
381 | void QQmlExpression::clearError() |
382 | { |
383 | Q_D(QQmlExpression); |
384 | d->clearError(); |
385 | } |
386 | |
387 | /*! |
388 | Return any error from the last call to evaluate(). If there was no error, |
389 | this returns an invalid QQmlError instance. |
390 | |
391 | \sa hasError(), clearError() |
392 | */ |
393 | |
394 | QQmlError QQmlExpression::error() const |
395 | { |
396 | Q_D(const QQmlExpression); |
397 | return d->error(engine()); |
398 | } |
399 | |
400 | /*! |
401 | \fn void QQmlExpression::valueChanged() |
402 | |
403 | Emitted each time the expression value changes from the last time it was |
404 | evaluated. The expression must have been evaluated at least once (by |
405 | calling QQmlExpression::evaluate()) before this signal will be emitted. |
406 | */ |
407 | |
408 | void QQmlExpressionPrivate::expressionChanged() |
409 | { |
410 | Q_Q(QQmlExpression); |
411 | emit q->valueChanged(); |
412 | } |
413 | |
414 | QString QQmlExpressionPrivate::expressionIdentifier() const |
415 | { |
416 | return QLatin1Char('"') + expression + QLatin1Char('"'); |
417 | } |
418 | |
419 | QT_END_NAMESPACE |
420 | |
421 | #include <moc_qqmlexpression.cpp> |
422 | |