source: trunk/src/declarative/qml/qdeclarativeworkerscript.cpp

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

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

File size: 21.2 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
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 qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "private/qdeclarativeworkerscript_p.h"
43#include "private/qdeclarativelistmodel_p.h"
44#include "private/qdeclarativelistmodelworkeragent_p.h"
45#include "private/qdeclarativeengine_p.h"
46
47#include <QtCore/qcoreevent.h>
48#include <QtCore/qcoreapplication.h>
49#include <QtCore/qdebug.h>
50#include <QtScript/qscriptengine.h>
51#include <QtCore/qmutex.h>
52#include <QtCore/qwaitcondition.h>
53#include <QtScript/qscriptvalueiterator.h>
54#include <QtCore/qfile.h>
55#include <QtCore/qdatetime.h>
56#include <QtNetwork/qnetworkaccessmanager.h>
57#include <QtDeclarative/qdeclarativeinfo.h>
58#include "qdeclarativenetworkaccessmanagerfactory.h"
59
60
61QT_BEGIN_NAMESPACE
62
63class WorkerDataEvent : public QEvent
64{
65public:
66 enum Type { WorkerData = QEvent::User };
67
68 WorkerDataEvent(int workerId, const QVariant &data);
69 virtual ~WorkerDataEvent();
70
71 int workerId() const;
72 QVariant data() const;
73
74private:
75 int m_id;
76 QVariant m_data;
77};
78
79class WorkerLoadEvent : public QEvent
80{
81public:
82 enum Type { WorkerLoad = WorkerDataEvent::WorkerData + 1 };
83
84 WorkerLoadEvent(int workerId, const QUrl &url);
85
86 int workerId() const;
87 QUrl url() const;
88
89private:
90 int m_id;
91 QUrl m_url;
92};
93
94class WorkerRemoveEvent : public QEvent
95{
96public:
97 enum Type { WorkerRemove = WorkerLoadEvent::WorkerLoad + 1 };
98
99 WorkerRemoveEvent(int workerId);
100
101 int workerId() const;
102
103private:
104 int m_id;
105};
106
107class QDeclarativeWorkerScriptEnginePrivate : public QObject
108{
109 Q_OBJECT
110public:
111 enum WorkerEventTypes {
112 WorkerDestroyEvent = QEvent::User + 100
113 };
114
115 QDeclarativeWorkerScriptEnginePrivate(QDeclarativeEngine *eng);
116
117 struct ScriptEngine : public QDeclarativeScriptEngine
118 {
119 ScriptEngine(QDeclarativeWorkerScriptEnginePrivate *parent) : QDeclarativeScriptEngine(0), p(parent), accessManager(0) {}
120 ~ScriptEngine() { delete accessManager; }
121 QDeclarativeWorkerScriptEnginePrivate *p;
122 QNetworkAccessManager *accessManager;
123
124 virtual QNetworkAccessManager *networkAccessManager() {
125 if (!accessManager) {
126 if (p->qmlengine && p->qmlengine->networkAccessManagerFactory()) {
127 accessManager = p->qmlengine->networkAccessManagerFactory()->create(this);
128 } else {
129 accessManager = new QNetworkAccessManager(this);
130 }
131 }
132 return accessManager;
133 }
134 };
135 ScriptEngine *workerEngine;
136 static QDeclarativeWorkerScriptEnginePrivate *get(QScriptEngine *e) {
137 return static_cast<ScriptEngine *>(e)->p;
138 }
139
140 QDeclarativeEngine *qmlengine;
141
142 QMutex m_lock;
143 QWaitCondition m_wait;
144
145 struct WorkerScript {
146 WorkerScript();
147
148 int id;
149 bool initialized;
150 QDeclarativeWorkerScript *owner;
151 QScriptValue object;
152
153 QScriptValue callback;
154 };
155
156 QHash<int, WorkerScript *> workers;
157 QScriptValue getWorker(int);
158
159 int m_nextId;
160
161 static QVariant scriptValueToVariant(const QScriptValue &);
162 static QScriptValue variantToScriptValue(const QVariant &, QScriptEngine *);
163
164 static QScriptValue onMessage(QScriptContext *ctxt, QScriptEngine *engine);
165 static QScriptValue sendMessage(QScriptContext *ctxt, QScriptEngine *engine);
166
167signals:
168 void stopThread();
169
170protected:
171 virtual bool event(QEvent *);
172
173private:
174 void processMessage(int, const QVariant &);
175 void processLoad(int, const QUrl &);
176};
177
178QDeclarativeWorkerScriptEnginePrivate::QDeclarativeWorkerScriptEnginePrivate(QDeclarativeEngine *engine)
179: workerEngine(0), qmlengine(engine), m_nextId(0)
180{
181}
182
183QScriptValue QDeclarativeWorkerScriptEnginePrivate::onMessage(QScriptContext *ctxt, QScriptEngine *engine)
184{
185 QDeclarativeWorkerScriptEnginePrivate *p = QDeclarativeWorkerScriptEnginePrivate::get(engine);
186
187 int id = ctxt->thisObject().data().toVariant().toInt();
188
189 WorkerScript *script = p->workers.value(id);
190 if (!script)
191 return engine->undefinedValue();
192
193 if (ctxt->argumentCount() >= 1)
194 script->callback = ctxt->argument(0);
195
196 return script->callback;
197}
198
199QScriptValue QDeclarativeWorkerScriptEnginePrivate::sendMessage(QScriptContext *ctxt, QScriptEngine *engine)
200{
201 if (!ctxt->argumentCount())
202 return engine->undefinedValue();
203
204 QDeclarativeWorkerScriptEnginePrivate *p = QDeclarativeWorkerScriptEnginePrivate::get(engine);
205
206 int id = ctxt->thisObject().data().toVariant().toInt();
207
208 WorkerScript *script = p->workers.value(id);
209 if (!script)
210 return engine->undefinedValue();
211
212 QMutexLocker(&p->m_lock);
213
214 if (script->owner)
215 QCoreApplication::postEvent(script->owner,
216 new WorkerDataEvent(0, scriptValueToVariant(ctxt->argument(0))));
217
218 return engine->undefinedValue();
219}
220
221QScriptValue QDeclarativeWorkerScriptEnginePrivate::getWorker(int id)
222{
223 QHash<int, WorkerScript *>::ConstIterator iter = workers.find(id);
224
225 if (iter == workers.end())
226 return workerEngine->nullValue();
227
228 WorkerScript *script = *iter;
229 if (!script->initialized) {
230
231 script->initialized = true;
232 script->object = workerEngine->newObject();
233
234 QScriptValue api = workerEngine->newObject();
235 api.setData(script->id);
236
237 api.setProperty(QLatin1String("onMessage"), workerEngine->newFunction(onMessage),
238 QScriptValue::PropertyGetter | QScriptValue::PropertySetter);
239 api.setProperty(QLatin1String("sendMessage"), workerEngine->newFunction(sendMessage));
240
241 script->object.setProperty(QLatin1String("WorkerScript"), api);
242 }
243
244 return script->object;
245}
246
247bool QDeclarativeWorkerScriptEnginePrivate::event(QEvent *event)
248{
249 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
250 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
251 processMessage(workerEvent->workerId(), workerEvent->data());
252 return true;
253 } else if (event->type() == (QEvent::Type)WorkerLoadEvent::WorkerLoad) {
254 WorkerLoadEvent *workerEvent = static_cast<WorkerLoadEvent *>(event);
255 processLoad(workerEvent->workerId(), workerEvent->url());
256 return true;
257 } else if (event->type() == (QEvent::Type)WorkerDestroyEvent) {
258 emit stopThread();
259 return true;
260 } else {
261 return QObject::event(event);
262 }
263}
264
265void QDeclarativeWorkerScriptEnginePrivate::processMessage(int id, const QVariant &data)
266{
267 WorkerScript *script = workers.value(id);
268 if (!script)
269 return;
270
271 if (script->callback.isFunction()) {
272 QScriptValue args = workerEngine->newArray(1);
273 args.setProperty(0, variantToScriptValue(data, workerEngine));
274
275 script->callback.call(script->object, args);
276 }
277}
278
279void QDeclarativeWorkerScriptEnginePrivate::processLoad(int id, const QUrl &url)
280{
281 if (url.isRelative())
282 return;
283
284 QString fileName = QDeclarativeEnginePrivate::urlToLocalFileOrQrc(url);
285
286 QFile f(fileName);
287 if (f.open(QIODevice::ReadOnly)) {
288 QByteArray data = f.readAll();
289 QString script = QString::fromUtf8(data);
290
291 QScriptValue activation = getWorker(id);
292
293 QScriptContext *ctxt = QScriptDeclarativeClass::pushCleanContext(workerEngine);
294 QScriptValue urlContext = workerEngine->newObject();
295 urlContext.setData(QScriptValue(workerEngine, fileName));
296 ctxt->pushScope(urlContext);
297 ctxt->pushScope(activation);
298 ctxt->setActivationObject(activation);
299 QDeclarativeScriptParser::extractPragmas(script);
300
301 workerEngine->baseUrl = url;
302 workerEngine->evaluate(script);
303
304 workerEngine->popContext();
305 } else {
306 qWarning().nospace() << "WorkerScript: Cannot find source file " << url.toString();
307 }
308}
309
310QVariant QDeclarativeWorkerScriptEnginePrivate::scriptValueToVariant(const QScriptValue &value)
311{
312 if (value.isBool()) {
313 return QVariant(value.toBool());
314 } else if (value.isString()) {
315 return QVariant(value.toString());
316 } else if (value.isNumber()) {
317 return QVariant((qreal)value.toNumber());
318 } else if (value.isDate()) {
319 return QVariant(value.toDateTime());
320#ifndef QT_NO_REGEXP
321 } else if (value.isRegExp()) {
322 return QVariant(value.toRegExp());
323#endif
324 } else if (value.isArray()) {
325 QVariantList list;
326
327 quint32 length = (quint32)value.property(QLatin1String("length")).toNumber();
328
329 for (quint32 ii = 0; ii < length; ++ii) {
330 QVariant v = scriptValueToVariant(value.property(ii));
331 list << v;
332 }
333
334 return QVariant(list);
335 } else if (value.isQObject()) {
336 QDeclarativeListModel *lm = qobject_cast<QDeclarativeListModel *>(value.toQObject());
337 if (lm) {
338 QDeclarativeListModelWorkerAgent *agent = lm->agent();
339 if (agent) {
340 QDeclarativeListModelWorkerAgent::VariantRef v(agent);
341 return qVariantFromValue(v);
342 } else {
343 return QVariant();
344 }
345 } else {
346 // No other QObject's are allowed to be sent
347 return QVariant();
348 }
349 } else if (value.isObject()) {
350 QVariantHash hash;
351
352 QScriptValueIterator iter(value);
353
354 while (iter.hasNext()) {
355 iter.next();
356 hash.insert(iter.name(), scriptValueToVariant(iter.value()));
357 }
358
359 return QVariant(hash);
360 }
361
362 return QVariant();
363
364}
365
366QScriptValue QDeclarativeWorkerScriptEnginePrivate::variantToScriptValue(const QVariant &value, QScriptEngine *engine)
367{
368 if (value.userType() == QVariant::Bool) {
369 return QScriptValue(value.toBool());
370 } else if (value.userType() == QVariant::String) {
371 return QScriptValue(value.toString());
372 } else if (value.userType() == QMetaType::QReal) {
373 return QScriptValue(value.toReal());
374 } else if (value.userType() == QVariant::DateTime) {
375 return engine->newDate(value.toDateTime());
376#ifndef QT_NO_REGEXP
377 } else if (value.userType() == QVariant::RegExp) {
378 return engine->newRegExp(value.toRegExp());
379#endif
380 } else if (value.userType() == qMetaTypeId<QDeclarativeListModelWorkerAgent::VariantRef>()) {
381 QDeclarativeListModelWorkerAgent::VariantRef vr = qvariant_cast<QDeclarativeListModelWorkerAgent::VariantRef>(value);
382 if (vr.a->scriptEngine() == 0)
383 vr.a->setScriptEngine(engine);
384 else if (vr.a->scriptEngine() != engine)
385 return engine->nullValue();
386 QScriptValue o = engine->newQObject(vr.a);
387 o.setData(engine->newVariant(value)); // Keeps the agent ref so that it is cleaned up on gc
388 return o;
389 } else if (value.userType() == QMetaType::QVariantList) {
390 QVariantList list = qvariant_cast<QVariantList>(value);
391 QScriptValue rv = engine->newArray(list.count());
392
393 for (quint32 ii = 0; ii < quint32(list.count()); ++ii)
394 rv.setProperty(ii, variantToScriptValue(list.at(ii), engine));
395
396 return rv;
397 } else if (value.userType() == QMetaType::QVariantHash) {
398
399 QVariantHash hash = qvariant_cast<QVariantHash>(value);
400
401 QScriptValue rv = engine->newObject();
402
403 for (QVariantHash::ConstIterator iter = hash.begin(); iter != hash.end(); ++iter)
404 rv.setProperty(iter.key(), variantToScriptValue(iter.value(), engine));
405
406 return rv;
407 } else {
408 return engine->nullValue();
409 }
410}
411
412WorkerDataEvent::WorkerDataEvent(int workerId, const QVariant &data)
413: QEvent((QEvent::Type)WorkerData), m_id(workerId), m_data(data)
414{
415}
416
417WorkerDataEvent::~WorkerDataEvent()
418{
419}
420
421int WorkerDataEvent::workerId() const
422{
423 return m_id;
424}
425
426QVariant WorkerDataEvent::data() const
427{
428 return m_data;
429}
430
431WorkerLoadEvent::WorkerLoadEvent(int workerId, const QUrl &url)
432: QEvent((QEvent::Type)WorkerLoad), m_id(workerId), m_url(url)
433{
434}
435
436int WorkerLoadEvent::workerId() const
437{
438 return m_id;
439}
440
441QUrl WorkerLoadEvent::url() const
442{
443 return m_url;
444}
445
446WorkerRemoveEvent::WorkerRemoveEvent(int workerId)
447: QEvent((QEvent::Type)WorkerRemove), m_id(workerId)
448{
449}
450
451int WorkerRemoveEvent::workerId() const
452{
453 return m_id;
454}
455
456QDeclarativeWorkerScriptEngine::QDeclarativeWorkerScriptEngine(QDeclarativeEngine *parent)
457: QThread(parent), d(new QDeclarativeWorkerScriptEnginePrivate(parent))
458{
459 d->m_lock.lock();
460 connect(d, SIGNAL(stopThread()), this, SLOT(quit()), Qt::DirectConnection);
461 start(QThread::IdlePriority);
462 d->m_wait.wait(&d->m_lock);
463 d->moveToThread(this);
464 d->m_lock.unlock();
465}
466
467QDeclarativeWorkerScriptEngine::~QDeclarativeWorkerScriptEngine()
468{
469 d->m_lock.lock();
470 qDeleteAll(d->workers);
471 d->workers.clear();
472 QCoreApplication::postEvent(d, new QEvent((QEvent::Type)QDeclarativeWorkerScriptEnginePrivate::WorkerDestroyEvent));
473 d->m_lock.unlock();
474
475 wait();
476 d->deleteLater();
477}
478
479QDeclarativeWorkerScriptEnginePrivate::WorkerScript::WorkerScript()
480: id(-1), initialized(false), owner(0)
481{
482}
483
484int QDeclarativeWorkerScriptEngine::registerWorkerScript(QDeclarativeWorkerScript *owner)
485{
486 QDeclarativeWorkerScriptEnginePrivate::WorkerScript *script = new QDeclarativeWorkerScriptEnginePrivate::WorkerScript;
487 script->id = d->m_nextId++;
488 script->owner = owner;
489
490 d->m_lock.lock();
491 d->workers.insert(script->id, script);
492 d->m_lock.unlock();
493
494 return script->id;
495}
496
497void QDeclarativeWorkerScriptEngine::removeWorkerScript(int id)
498{
499 QCoreApplication::postEvent(d, new WorkerRemoveEvent(id));
500}
501
502void QDeclarativeWorkerScriptEngine::executeUrl(int id, const QUrl &url)
503{
504 QCoreApplication::postEvent(d, new WorkerLoadEvent(id, url));
505}
506
507void QDeclarativeWorkerScriptEngine::sendMessage(int id, const QVariant &data)
508{
509 QCoreApplication::postEvent(d, new WorkerDataEvent(id, data));
510}
511
512void QDeclarativeWorkerScriptEngine::run()
513{
514 d->m_lock.lock();
515
516 d->workerEngine = new QDeclarativeWorkerScriptEnginePrivate::ScriptEngine(d);
517
518 d->m_wait.wakeAll();
519
520 d->m_lock.unlock();
521
522 exec();
523
524 delete d->workerEngine; d->workerEngine = 0;
525}
526
527
528/*!
529 \qmlclass WorkerScript QDeclarativeWorkerScript
530 \ingroup qml-utility-elements
531 \brief The WorkerScript element enables the use of threads in QML.
532
533 Use WorkerScript to run operations in a new thread.
534 This is useful for running operations in the background so
535 that the main GUI thread is not blocked.
536
537 Messages can be passed between the new thread and the parent thread
538 using \l sendMessage() and the \l {WorkerScript::onMessage}{onMessage()} handler.
539
540 An example:
541
542 \snippet doc/src/snippets/declarative/workerscript.qml 0
543
544 The above worker script specifies a JavaScript file, "script.js", that handles
545 the operations to be performed in the new thread. Here is \c script.js:
546
547 \qml
548 WorkerScript.onMessage = function(message) {
549 // ... long-running operations and calculations are done here
550 WorkerScript.sendMessage({ 'reply': 'Mouse is at ' + message.x + ',' + message.y })
551 }
552 \endqml
553
554 When the user clicks anywhere within the rectangle, \c sendMessage() is
555 called, triggering the \tt WorkerScript.onMessage() handler in
556 \tt script.js. This in turn sends a reply message that is then received
557 by the \tt onMessage() handler of \tt myWorker.
558
559
560 \section3 Restrictions
561
562 Since the \c WorkerScript.onMessage() function is run in a separate thread, the
563 JavaScript file is evaluated in a context separate from the main QML engine. This means
564 that unlike an ordinary JavaScript file that is imported into QML, the \c script.js
565 in the above example cannot access the properties, methods or other attributes
566 of the QML item, nor can it access any context properties set on the QML object
567 through QDeclarativeContext.
568
569 Additionally, there are restrictions on the types of values that can be passed to and
570 from the worker script. See the sendMessage() documentation for details.
571
572 \sa {declarative/threading/workerscript}{WorkerScript example},
573 {declarative/threading/threadedlistmodel}{Threaded ListModel example}
574*/
575QDeclarativeWorkerScript::QDeclarativeWorkerScript(QObject *parent)
576: QObject(parent), m_engine(0), m_scriptId(-1), m_componentComplete(true)
577{
578}
579
580QDeclarativeWorkerScript::~QDeclarativeWorkerScript()
581{
582 if (m_scriptId != -1) m_engine->removeWorkerScript(m_scriptId);
583}
584
585/*!
586 \qmlproperty url WorkerScript::source
587
588 This holds the url of the JavaScript file that implements the
589 \tt WorkerScript.onMessage() handler for threaded operations.
590*/
591QUrl QDeclarativeWorkerScript::source() const
592{
593 return m_source;
594}
595
596void QDeclarativeWorkerScript::setSource(const QUrl &source)
597{
598 if (m_source == source)
599 return;
600
601 m_source = source;
602
603 if (engine())
604 m_engine->executeUrl(m_scriptId, m_source);
605
606 emit sourceChanged();
607}
608
609/*!
610 \qmlmethod WorkerScript::sendMessage(jsobject message)
611
612 Sends the given \a message to a worker script handler in another
613 thread. The other worker script handler can receive this message
614 through the onMessage() handler.
615
616 The \c message object may only contain values of the following
617 types:
618
619 \list
620 \o boolean, number, string
621 \o JavaScript objects and arrays
622 \o ListModel objects (any other type of QObject* is not allowed)
623 \endlist
624
625 All objects and arrays are copied to the \c message. With the exception
626 of ListModel objects, any modifications by the other thread to an object
627 passed in \c message will not be reflected in the original object.
628*/
629void QDeclarativeWorkerScript::sendMessage(const QScriptValue &message)
630{
631 if (!engine()) {
632 qWarning("QDeclarativeWorkerScript: Attempt to send message before WorkerScript establishment");
633 return;
634 }
635
636 m_engine->sendMessage(m_scriptId, QDeclarativeWorkerScriptEnginePrivate::scriptValueToVariant(message));
637}
638
639void QDeclarativeWorkerScript::classBegin()
640{
641 m_componentComplete = false;
642}
643
644QDeclarativeWorkerScriptEngine *QDeclarativeWorkerScript::engine()
645{
646 if (m_engine) return m_engine;
647 if (m_componentComplete) {
648 QDeclarativeEngine *engine = qmlEngine(this);
649 if (!engine) {
650 qWarning("QDeclarativeWorkerScript: engine() called without qmlEngine() set");
651 return 0;
652 }
653
654 m_engine = QDeclarativeEnginePrivate::get(engine)->getWorkerScriptEngine();
655 m_scriptId = m_engine->registerWorkerScript(this);
656
657 if (m_source.isValid())
658 m_engine->executeUrl(m_scriptId, m_source);
659
660 return m_engine;
661 }
662 return 0;
663}
664
665void QDeclarativeWorkerScript::componentComplete()
666{
667 m_componentComplete = true;
668 engine(); // Get it started now.
669}
670
671/*!
672 \qmlsignal WorkerScript::onMessage(jsobject msg)
673
674 This handler is called when a message \a msg is received from a worker
675 script in another thread through a call to sendMessage().
676*/
677
678bool QDeclarativeWorkerScript::event(QEvent *event)
679{
680 if (event->type() == (QEvent::Type)WorkerDataEvent::WorkerData) {
681 QDeclarativeEngine *engine = qmlEngine(this);
682 if (engine) {
683 QScriptEngine *scriptEngine = QDeclarativeEnginePrivate::getScriptEngine(engine);
684 WorkerDataEvent *workerEvent = static_cast<WorkerDataEvent *>(event);
685 QScriptValue value =
686 QDeclarativeWorkerScriptEnginePrivate::variantToScriptValue(workerEvent->data(), scriptEngine);
687 emit message(value);
688 }
689 return true;
690 } else {
691 return QObject::event(event);
692 }
693}
694
695QT_END_NAMESPACE
696
697#include <qdeclarativeworkerscript.moc>
698
Note: See TracBrowser for help on using the repository browser.