source: trunk/src/declarative/qml/qdeclarativeexpression.cpp@ 1107

Last change on this file since 1107 was 846, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 26.7 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtDeclarative module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qdeclarativeexpression.h"
43#include "private/qdeclarativeexpression_p.h"
44
45#include "private/qdeclarativeengine_p.h"
46#include "private/qdeclarativecontext_p.h"
47#include "private/qdeclarativerewrite_p.h"
48#include "private/qdeclarativecompiler_p.h"
49#include "private/qdeclarativeglobalscriptclass_p.h"
50
51#include <QtCore/qdebug.h>
52#include <QtScript/qscriptprogram.h>
53
54#include <private/qscriptdeclarativeclass_p.h>
55
56QT_BEGIN_NAMESPACE
57
58bool QDeclarativeDelayedError::addError(QDeclarativeEnginePrivate *e)
59{
60 if (!e) return false;
61
62 if (e->inProgressCreations == 0) return false; // Not in construction
63
64 if (prevError) return true; // Already in error chain
65
66 prevError = &e->erroredBindings;
67 nextError = e->erroredBindings;
68 e->erroredBindings = this;
69 if (nextError) nextError->prevError = &nextError;
70
71 return true;
72}
73
74QDeclarativeQtScriptExpression::QDeclarativeQtScriptExpression()
75: dataRef(0), expressionFunctionMode(ExplicitContext), scopeObject(0), trackChange(false),
76 guardList(0), guardListLength(0), guardObject(0), guardObjectNotifyIndex(-1), deleted(0)
77{
78}
79
80QDeclarativeQtScriptExpression::~QDeclarativeQtScriptExpression()
81{
82 if (guardList) { delete [] guardList; guardList = 0; }
83 if (dataRef) dataRef->release();
84 if (deleted) *deleted = true;
85}
86
87QDeclarativeExpressionPrivate::QDeclarativeExpressionPrivate()
88: expressionFunctionValid(true), line(-1)
89{
90}
91
92QDeclarativeExpressionPrivate::~QDeclarativeExpressionPrivate()
93{
94}
95
96void QDeclarativeExpressionPrivate::init(QDeclarativeContextData *ctxt, const QString &expr,
97 QObject *me)
98{
99 expression = expr;
100
101 QDeclarativeAbstractExpression::setContext(ctxt);
102 scopeObject = me;
103 expressionFunctionValid = false;
104}
105
106void QDeclarativeExpressionPrivate::init(QDeclarativeContextData *ctxt, void *expr,
107 QDeclarativeRefCount *rc,
108 QObject *me, const QString &srcUrl, int lineNumber)
109{
110 url = srcUrl;
111 line = lineNumber;
112
113 if (dataRef) dataRef->release();
114 dataRef = rc;
115 if (dataRef) dataRef->addref();
116
117 quint32 *exprData = (quint32 *)expr;
118 QDeclarativeCompiledData *dd = (QDeclarativeCompiledData *)rc;
119
120 expression = QString::fromRawData((QChar *)(exprData + 2), exprData[1]);
121
122 int progIdx = *(exprData);
123 bool isSharedProgram = progIdx & 0x80000000;
124 progIdx &= 0x7FFFFFFF;
125
126 QDeclarativeEngine *engine = ctxt->engine;
127 QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine);
128 QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine);
129
130 if (isSharedProgram) {
131
132 if (!dd->cachedClosures.at(progIdx)) {
133 QScriptContext *scriptContext = QScriptDeclarativeClass::pushCleanContext(scriptEngine);
134 scriptContext->pushScope(ep->contextClass->newSharedContext());
135 scriptContext->pushScope(ep->globalClass->staticGlobalObject());
136 dd->cachedClosures[progIdx] = new QScriptValue(scriptEngine->evaluate(expression, url, line));
137 scriptEngine->popContext();
138 }
139
140 expressionFunction = *dd->cachedClosures.at(progIdx);
141 expressionFunctionMode = SharedContext;
142 expressionFunctionValid = true;
143
144 } else {
145
146 if (!dd->cachedPrograms.at(progIdx)) {
147 dd->cachedPrograms[progIdx] = new QScriptProgram(expression, url, line);
148 }
149
150 expressionFunction = evalInObjectScope(ctxt, me, *dd->cachedPrograms.at(progIdx),
151 &expressionContext);
152
153 expressionFunctionMode = ExplicitContext;
154 expressionFunctionValid = true;
155 }
156
157 QDeclarativeAbstractExpression::setContext(ctxt);
158 scopeObject = me;
159}
160
161QScriptValue QDeclarativeExpressionPrivate::evalInObjectScope(QDeclarativeContextData *context, QObject *object,
162 const QString &program, const QString &fileName,
163 int lineNumber, QScriptValue *contextObject)
164{
165 QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(context->engine);
166 QScriptContext *scriptContext = QScriptDeclarativeClass::pushCleanContext(&ep->scriptEngine);
167 if (contextObject) {
168 *contextObject = ep->contextClass->newContext(context, object);
169 scriptContext->pushScope(*contextObject);
170 } else {
171 scriptContext->pushScope(ep->contextClass->newContext(context, object));
172 }
173 scriptContext->pushScope(ep->globalClass->staticGlobalObject());
174 QScriptValue rv = ep->scriptEngine.evaluate(program, fileName, lineNumber);
175 ep->scriptEngine.popContext();
176 return rv;
177}
178
179QScriptValue QDeclarativeExpressionPrivate::evalInObjectScope(QDeclarativeContextData *context, QObject *object,
180 const QScriptProgram &program,
181 QScriptValue *contextObject)
182{
183 QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(context->engine);
184 QScriptContext *scriptContext = QScriptDeclarativeClass::pushCleanContext(&ep->scriptEngine);
185 if (contextObject) {
186 *contextObject = ep->contextClass->newContext(context, object);
187 scriptContext->pushScope(*contextObject);
188 } else {
189 scriptContext->pushScope(ep->contextClass->newContext(context, object));
190 }
191 scriptContext->pushScope(ep->globalClass->staticGlobalObject());
192 QScriptValue rv = ep->scriptEngine.evaluate(program);
193 ep->scriptEngine.popContext();
194 return rv;
195}
196
197/*!
198 \class QDeclarativeExpression
199 \since 4.7
200 \brief The QDeclarativeExpression class evaluates JavaScript in a QML context.
201
202 For example, given a file \c main.qml like this:
203
204 \qml
205 import QtQuick 1.0
206
207 Item {
208 width: 200; height: 200
209 }
210 \endqml
211
212 The following code evaluates a JavaScript expression in the context of the
213 above QML:
214
215 \code
216 QDeclarativeEngine *engine = new QDeclarativeEngine;
217 QDeclarativeComponent component(engine, QUrl::fromLocalFile("main.qml"));
218
219 QObject *myObject = component.create();
220 QDeclarativeExpression *expr = new QDeclarativeExpression(engine->rootContext(), myObject, "width * 2");
221 int result = expr->evaluate().toInt(); // result = 400
222 \endcode
223*/
224
225static int QDeclarativeExpression_notifyIdx = -1;
226
227/*!
228 Create an invalid QDeclarativeExpression.
229
230 As the expression will not have an associated QDeclarativeContext, this will be a
231 null expression object and its value will always be an invalid QVariant.
232 */
233QDeclarativeExpression::QDeclarativeExpression()
234: QObject(*new QDeclarativeExpressionPrivate, 0)
235{
236 Q_D(QDeclarativeExpression);
237
238 if (QDeclarativeExpression_notifyIdx == -1)
239 QDeclarativeExpression_notifyIdx = QDeclarativeExpression::staticMetaObject.indexOfMethod("_q_notify()");
240 d->setNotifyObject(this, QDeclarativeExpression_notifyIdx);
241}
242
243/*! \internal */
244QDeclarativeExpression::QDeclarativeExpression(QDeclarativeContextData *ctxt, void *expr,
245 QDeclarativeRefCount *rc, QObject *me,
246 const QString &url, int lineNumber,
247 QDeclarativeExpressionPrivate &dd)
248: QObject(dd, 0)
249{
250 Q_D(QDeclarativeExpression);
251 d->init(ctxt, expr, rc, me, url, lineNumber);
252
253 if (QDeclarativeExpression_notifyIdx == -1)
254 QDeclarativeExpression_notifyIdx = QDeclarativeExpression::staticMetaObject.indexOfMethod("_q_notify()");
255 d->setNotifyObject(this, QDeclarativeExpression_notifyIdx);
256}
257
258/*!
259 Create a QDeclarativeExpression object that is a child of \a parent.
260
261 The \a expression JavaScript will be executed in the \a ctxt QDeclarativeContext.
262 If specified, the \a scope object's properties will also be in scope during
263 the expression's execution.
264*/
265QDeclarativeExpression::QDeclarativeExpression(QDeclarativeContext *ctxt,
266 QObject *scope,
267 const QString &expression,
268 QObject *parent)
269: QObject(*new QDeclarativeExpressionPrivate, parent)
270{
271 Q_D(QDeclarativeExpression);
272 d->init(QDeclarativeContextData::get(ctxt), expression, scope);
273
274 if (QDeclarativeExpression_notifyIdx == -1)
275 QDeclarativeExpression_notifyIdx = QDeclarativeExpression::staticMetaObject.indexOfMethod("_q_notify()");
276 d->setNotifyObject(this, QDeclarativeExpression_notifyIdx);
277}
278
279/*!
280 \internal
281*/
282QDeclarativeExpression::QDeclarativeExpression(QDeclarativeContextData *ctxt, QObject *scope,
283 const QString &expression)
284: QObject(*new QDeclarativeExpressionPrivate, 0)
285{
286 Q_D(QDeclarativeExpression);
287 d->init(ctxt, expression, scope);
288
289 if (QDeclarativeExpression_notifyIdx == -1)
290 QDeclarativeExpression_notifyIdx = QDeclarativeExpression::staticMetaObject.indexOfMethod("_q_notify()");
291 d->setNotifyObject(this, QDeclarativeExpression_notifyIdx);
292}
293
294/*! \internal */
295QDeclarativeExpression::QDeclarativeExpression(QDeclarativeContextData *ctxt, QObject *scope,
296 const QString &expression, QDeclarativeExpressionPrivate &dd)
297: QObject(dd, 0)
298{
299 Q_D(QDeclarativeExpression);
300 d->init(ctxt, expression, scope);
301
302 if (QDeclarativeExpression_notifyIdx == -1)
303 QDeclarativeExpression_notifyIdx = QDeclarativeExpression::staticMetaObject.indexOfMethod("_q_notify()");
304 d->setNotifyObject(this, QDeclarativeExpression_notifyIdx);
305}
306
307/*!
308 Destroy the QDeclarativeExpression instance.
309*/
310QDeclarativeExpression::~QDeclarativeExpression()
311{
312}
313
314/*!
315 Returns the QDeclarativeEngine this expression is associated with, or 0 if there
316 is no association or the QDeclarativeEngine has been destroyed.
317*/
318QDeclarativeEngine *QDeclarativeExpression::engine() const
319{
320 Q_D(const QDeclarativeExpression);
321 return d->context()?d->context()->engine:0;
322}
323
324/*!
325 Returns the QDeclarativeContext this expression is associated with, or 0 if there
326 is no association or the QDeclarativeContext has been destroyed.
327*/
328QDeclarativeContext *QDeclarativeExpression::context() const
329{
330 Q_D(const QDeclarativeExpression);
331 QDeclarativeContextData *data = d->context();
332 return data?data->asQDeclarativeContext():0;
333}
334
335/*!
336 Returns the expression string.
337*/
338QString QDeclarativeExpression::expression() const
339{
340 Q_D(const QDeclarativeExpression);
341 return d->expression;
342}
343
344/*!
345 Set the expression to \a expression.
346*/
347void QDeclarativeExpression::setExpression(const QString &expression)
348{
349 Q_D(QDeclarativeExpression);
350
351 d->resetNotifyOnChange();
352 d->expression = expression;
353 d->expressionFunctionValid = false;
354 d->expressionFunction = QScriptValue();
355}
356
357void QDeclarativeExpressionPrivate::exceptionToError(QScriptEngine *scriptEngine,
358 QDeclarativeError &error)
359{
360 if (scriptEngine->hasUncaughtException() &&
361 scriptEngine->uncaughtException().isError()) {
362
363 QString fileName;
364 int lineNumber = scriptEngine->uncaughtExceptionLineNumber();
365
366 QScriptValue exception = scriptEngine->uncaughtException();
367 QLatin1String fileNameProp("fileName");
368
369 if (!exception.property(fileNameProp).toString().isEmpty()){
370 fileName = exception.property(fileNameProp).toString();
371 } else {
372 fileName = QLatin1String("<Unknown File>");
373 }
374
375 error.setUrl(QUrl(fileName));
376 error.setLine(lineNumber);
377 error.setColumn(-1);
378 error.setDescription(exception.toString());
379 } else {
380 error = QDeclarativeError();
381 }
382}
383
384bool QDeclarativeQtScriptExpression::notifyOnValueChange() const
385{
386 return trackChange;
387}
388
389void QDeclarativeQtScriptExpression::setNotifyOnValueChange(bool notify)
390{
391 trackChange = notify;
392 if (!notify && guardList)
393 clearGuards();
394}
395
396void QDeclarativeQtScriptExpression::resetNotifyOnChange()
397{
398 clearGuards();
399}
400
401void QDeclarativeQtScriptExpression::setNotifyObject(QObject *object, int notifyIndex)
402{
403 if (guardList) clearGuards();
404
405 if (!object || notifyIndex == -1) {
406 guardObject = 0;
407 notifyIndex = -1;
408 } else {
409 guardObject = object;
410 guardObjectNotifyIndex = notifyIndex;
411
412 }
413}
414
415QScriptValue QDeclarativeQtScriptExpression::scriptValue(QObject *secondaryScope, bool *isUndefined)
416{
417 Q_ASSERT(context() && context()->engine);
418 Q_ASSERT(!trackChange || (guardObject && guardObjectNotifyIndex != -1));
419
420 if (!expressionFunction.isValid()) {
421 if (isUndefined) *isUndefined = true;
422 return QScriptValue();
423 }
424
425 DeleteWatcher watcher(this);
426
427 QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(context()->engine);
428
429 bool lastCaptureProperties = ep->captureProperties;
430 QPODVector<QDeclarativeEnginePrivate::CapturedProperty> lastCapturedProperties;
431 ep->captureProperties = trackChange;
432 ep->capturedProperties.copyAndClear(lastCapturedProperties);
433
434 QScriptValue value = eval(secondaryScope, isUndefined);
435
436 if (!watcher.wasDeleted() && trackChange) {
437 if (ep->capturedProperties.count() == 0) {
438
439 if (guardList) clearGuards();
440
441 } else {
442
443 updateGuards(ep->capturedProperties);
444
445 }
446 }
447
448 lastCapturedProperties.copyAndClear(ep->capturedProperties);
449 ep->captureProperties = lastCaptureProperties;
450
451 return value;
452}
453
454QScriptValue QDeclarativeQtScriptExpression::eval(QObject *secondaryScope, bool *isUndefined)
455{
456 Q_ASSERT(context() && context()->engine);
457
458 DeleteWatcher watcher(this);
459
460 QDeclarativeEngine *engine = context()->engine;
461 QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine);
462
463 QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine);
464
465 QDeclarativeContextData *oldSharedContext = 0;
466 QObject *oldSharedScope = 0;
467 QObject *oldOverride = 0;
468 bool isShared = (expressionFunctionMode == SharedContext);
469
470 if (isShared) {
471 oldSharedContext = ep->sharedContext;
472 oldSharedScope = ep->sharedScope;
473 ep->sharedContext = context();
474 ep->sharedScope = scopeObject;
475 } else {
476 oldOverride = ep->contextClass->setOverrideObject(expressionContext, secondaryScope);
477 }
478
479 QScriptValue svalue = expressionFunction.call(); // This could cause this to be deleted
480
481 if (isShared) {
482 ep->sharedContext = oldSharedContext;
483 ep->sharedScope = oldSharedScope;
484 } else if (!watcher.wasDeleted()) {
485 ep->contextClass->setOverrideObject(expressionContext, oldOverride);
486 }
487
488 if (isUndefined)
489 *isUndefined = svalue.isUndefined() || scriptEngine->hasUncaughtException();
490
491 // Handle exception
492 if (scriptEngine->hasUncaughtException()) {
493 if (!watcher.wasDeleted())
494 QDeclarativeExpressionPrivate::exceptionToError(scriptEngine, error);
495
496 scriptEngine->clearExceptions();
497 return QScriptValue();
498 } else {
499 if (!watcher.wasDeleted())
500 error = QDeclarativeError();
501
502 return svalue;
503 }
504}
505
506void QDeclarativeQtScriptExpression::updateGuards(const QPODVector<QDeclarativeEnginePrivate::CapturedProperty> &properties)
507{
508 Q_ASSERT(guardObject);
509 Q_ASSERT(guardObjectNotifyIndex != -1);
510
511 if (properties.count() != guardListLength) {
512 QDeclarativeNotifierEndpoint *newGuardList = new QDeclarativeNotifierEndpoint[properties.count()];
513
514 for (int ii = 0; ii < qMin(guardListLength, properties.count()); ++ii)
515 guardList[ii].copyAndClear(newGuardList[ii]);
516
517 delete [] guardList;
518 guardList = newGuardList;
519 guardListLength = properties.count();
520 }
521
522 bool outputWarningHeader = false;
523 bool noChanges = true;
524 for (int ii = 0; ii < properties.count(); ++ii) {
525 QDeclarativeNotifierEndpoint &guard = guardList[ii];
526 const QDeclarativeEnginePrivate::CapturedProperty &property = properties.at(ii);
527
528 guard.target = guardObject;
529 guard.targetMethod = guardObjectNotifyIndex;
530
531 if (property.notifier != 0) {
532
533 if (!noChanges && guard.isConnected(property.notifier)) {
534 // Nothing to do
535
536 } else {
537 noChanges = false;
538
539 bool existing = false;
540 for (int jj = 0; !existing && jj < ii; ++jj)
541 if (guardList[jj].isConnected(property.notifier))
542 existing = true;
543
544 if (existing) {
545 // duplicate
546 guard.disconnect();
547 } else {
548 guard.connect(property.notifier);
549 }
550 }
551
552
553 } else if (property.notifyIndex != -1) {
554
555 if (!noChanges && guard.isConnected(property.object, property.notifyIndex)) {
556 // Nothing to do
557
558 } else {
559 noChanges = false;
560
561 bool existing = false;
562 for (int jj = 0; !existing && jj < ii; ++jj)
563 if (guardList[jj].isConnected(property.object, property.notifyIndex))
564 existing = true;
565
566 if (existing) {
567 // duplicate
568 guard.disconnect();
569 } else {
570 guard.connect(property.object, property.notifyIndex);
571 }
572 }
573
574 } else {
575 if (!outputWarningHeader) {
576 outputWarningHeader = true;
577 qWarning() << "QDeclarativeExpression: Expression" << expression
578 << "depends on non-NOTIFYable properties:";
579 }
580
581 const QMetaObject *metaObj = property.object->metaObject();
582 QMetaProperty metaProp = metaObj->property(property.coreIndex);
583
584 qWarning().nospace() << " " << metaObj->className() << "::" << metaProp.name();
585 }
586 }
587}
588
589QScriptValue QDeclarativeExpressionPrivate::scriptValue(QObject *secondaryScope, bool *isUndefined)
590{
591 if (!expressionFunctionValid) {
592 QDeclarativeEngine *engine = context()->engine;
593 QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(engine);
594
595 QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine);
596
597 QScriptContext *scriptContext = QScriptDeclarativeClass::pushCleanContext(scriptEngine);
598 expressionContext = ep->contextClass->newContext(context(), scopeObject);
599 scriptContext->pushScope(expressionContext);
600 scriptContext->pushScope(ep->globalClass->staticGlobalObject());
601
602 QDeclarativeRewrite::RewriteBinding rewriteBinding;
603 rewriteBinding.setName(name);
604 bool ok = true;
605 const QString code = rewriteBinding(expression, &ok);
606 if (ok)
607 expressionFunction = scriptEngine->evaluate(code, url, line);
608
609 scriptEngine->popContext();
610 expressionFunctionMode = ExplicitContext;
611 expressionFunctionValid = true;
612 }
613
614 return QDeclarativeQtScriptExpression::scriptValue(secondaryScope, isUndefined);
615}
616
617QVariant QDeclarativeExpressionPrivate::value(QObject *secondaryScope, bool *isUndefined)
618{
619 Q_Q(QDeclarativeExpression);
620
621 if (!context() || !context()->isValid()) {
622 qWarning("QDeclarativeExpression: Attempted to evaluate an expression in an invalid context");
623 return QVariant();
624 }
625
626 QDeclarativeEnginePrivate *ep = QDeclarativeEnginePrivate::get(q->engine());
627
628 return ep->scriptValueToVariant(scriptValue(secondaryScope, isUndefined), qMetaTypeId<QList<QObject*> >());
629}
630