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