}
QQmlBoundSignal::QQmlBoundSignal(QObject *scope, const QMetaMethod &signal,
- QObject *owner)
+ QObject *owner, QQmlEngine *engine)
: m_expression(0), m_params(0), m_scope(scope), m_index(signal.methodIndex())
{
setParamsValid(false);
setIsEvaluating(false);
addToObject(owner);
callback = &subscriptionCallback;
- QQmlNotifierEndpoint::connect(scope, m_index);
+ QQmlNotifierEndpoint::connect(scope, m_index, engine);
}
QQmlBoundSignal::~QQmlBoundSignal()
public QQmlNotifierEndpoint
{
public:
- QQmlBoundSignal(QObject *scope, const QMetaMethod &signal, QObject *owner);
+ QQmlBoundSignal(QObject *scope, const QMetaMethod &signal, QObject *owner, QQmlEngine *engine);
virtual ~QQmlBoundSignal();
int index() const;
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdir.h>
#include <QtCore/qmutex.h>
+#include <QtCore/qthread.h>
+#include <private/qthread_p.h>
#include <QtNetwork/qnetworkconfigmanager.h>
#include <private/qobject_p.h>
QQmlData *ddata = QQmlData::get(object, false);
if (!ddata) return; // Probably being deleted
- QQmlNotifierEndpoint *ep = ddata->notify(index);
- if (ep) QQmlNotifier::emitNotify(ep, a);
+ // In general, QML only supports QObject's that live on the same thread as the QQmlEngine
+ // that they're exposed to. However, to make writing "worker objects" that calculate data
+ // in a separate thread easier, QML allows a QObject that lives in the same thread as the
+ // QQmlEngine to emit signals from a different thread. These signals are then automatically
+ // marshalled back onto the QObject's thread and handled by QML from there. This is tested
+ // by the qqmlecmascript::threadSignal() autotest.
+ if (ddata->notifyList &&
+ QThread::currentThreadId() != QObjectPrivate::get(object)->threadData->threadId) {
+
+ QMetaMethod m = object->metaObject()->method(index);
+ QList<QByteArray> parameterTypes = m.parameterTypes();
+
+ int *types = (int *)malloc((parameterTypes.count() + 1) * sizeof(int));
+ void **args = (void **) malloc((parameterTypes.count() + 1) *sizeof(void *));
+
+ types[0] = 0; // return type
+ args[0] = 0; // return value
+
+ for (int ii = 0; ii < parameterTypes.count(); ++ii) {
+ const QByteArray &typeName = parameterTypes.at(ii);
+ if (typeName.endsWith('*'))
+ types[ii + 1] = QMetaType::VoidStar;
+ else
+ types[ii + 1] = QMetaType::type(typeName);
+
+ if (!types[ii + 1]) {
+ qWarning("QObject::connect: Cannot queue arguments of type '%s'\n"
+ "(Make sure '%s' is registered using qRegisterMetaType().)",
+ typeName.constData(), typeName.constData());
+ free(types);
+ free(args);
+ return;
+ }
+
+ args[ii + 1] = QMetaType::create(types[ii + 1], a[ii + 1]);
+ }
+
+ QMetaCallEvent *ev = new QMetaCallEvent(index, 0, 0, object, index,
+ parameterTypes.count() + 1, types, args);
+ QCoreApplication::postEvent(object, ev);
+
+ } else {
+ QQmlNotifierEndpoint *ep = ddata->notify(index);
+ if (ep) QQmlNotifier::emitNotify(ep, a);
+ }
}
int QQmlData::receivers(QAbstractDeclarativeData *d, const QObject *, int index)
Q_ASSERT(g->isConnected(o, n));
} else {
g = Guard::New(expression, engine);
- g->connect(o, n);
+ g->connect(o, n, engine);
}
expression->activeGuards.prepend(g);
#include "qqmlnotifier_p.h"
#include "qqmlproperty_p.h"
+#include <QtCore/qdebug.h>
+#include <private/qthread_p.h>
QT_BEGIN_NAMESPACE
else if (endpoint) endpoint->notifying = 0;
}
-void QQmlNotifierEndpoint::connect(QObject *source, int sourceSignal)
+void QQmlNotifierEndpoint::connect(QObject *source, int sourceSignal, QQmlEngine *engine)
{
disconnect();
+ Q_ASSERT(engine);
+ if (QObjectPrivate::get(source)->threadData->threadId !=
+ QObjectPrivate::get(engine)->threadData->threadId) {
+
+ QString sourceName;
+ QDebug(&sourceName) << source;
+ sourceName = sourceName.left(sourceName.length() - 1);
+ QString engineName;
+ QDebug(&engineName).nospace() << engine;
+ engineName = engineName.left(engineName.length() - 1);
+
+ qFatal("QQmlEngine: Illegal attempt to connect to %s that is in"
+ " a different thread than the QML engine %s.", qPrintable(sourceName),
+ qPrintable(engineName));
+ }
+
this->source = source;
this->sourceSignal = sourceSignal;
QQmlPropertyPrivate::flushSignal(source, sourceSignal);
QQmlNotifierEndpoint *endpoints;
};
+class QQmlEngine;
class QQmlNotifierEndpoint
{
public:
inline bool isConnected(QObject *source, int sourceSignal);
inline bool isConnected(QQmlNotifier *);
- void connect(QObject *source, int sourceSignal);
+ void connect(QObject *source, int sourceSignal, QQmlEngine *engine);
inline void connect(QQmlNotifier *);
inline void disconnect();
return signalHandler->takeExpression(expr);
if (expr) {
- QQmlBoundSignal *signal = new QQmlBoundSignal(that.d->object, that.method(), that.d->object);
+ QQmlBoundSignal *signal = new QQmlBoundSignal(that.d->object, that.method(), that.d->object,
+ expr->context()->engine);
signal->takeExpression(expr);
}
return 0;
QMetaMethod signal = target->metaObject()->method(instr.signalIndex);
- QQmlBoundSignal *bs = new QQmlBoundSignal(target, signal, target);
+ QQmlBoundSignal *bs = new QQmlBoundSignal(target, signal, target, engine);
QQmlBoundSignalExpression *expr =
new QQmlBoundSignalExpression(CTXT, context, DATAS.at(instr.value), true, COMP->name, instr.line, instr.column);
bs->takeExpression(expr);
QMetaProperty prop = target->metaObject()->property(d->propertyIndex());
if (prop.hasNotifySignal())
- connect(target, prop.notifySignalIndex());
+ connect(target, prop.notifySignalIndex(), ctxt->engine);
}
metaObject.setFlag();
}
}
-void QV4Bindings::subscribe(QObject *o, int notifyIndex, int subIndex)
+void QV4Bindings::subscribe(QObject *o, int notifyIndex, int subIndex, QQmlEngine *e)
{
Subscription *sub = (subscriptions + subIndex);
if (sub->isConnected(o, notifyIndex))
sub->bindings = this;
sub->method = subIndex;
if (o)
- sub->connect(o, notifyIndex);
+ sub->connect(o, notifyIndex, e);
else
sub->disconnect();
}
QObject *o = 0;
const Register &object = registers[instr->subscribeop.reg];
if (!object.isUndefined()) o = object.getQObject();
- subscribe(o, instr->subscribeop.index, instr->subscribeop.offset);
+ subscribe(o, instr->subscribeop.index, instr->subscribeop.offset, context->engine);
}
QML_V4_END_INSTR(Subscribe, subscribeop)
accessors->notifier(object, instr->fetchAndSubscribe.property.accessorData, ¬ifier);
if (notifier) sub->connect(notifier);
} else if (instr->fetchAndSubscribe.property.notifyIndex != -1) {
- sub->connect(object, instr->fetchAndSubscribe.property.notifyIndex);
+ sub->connect(object, instr->fetchAndSubscribe.property.notifyIndex, context->engine);
}
}
}
inline void unsubscribe(int subIndex);
inline void subscribeId(QQmlContextData *p, int idIndex, int subIndex);
- inline void subscribe(QObject *o, int notifyIndex, int subIndex);
+ inline void subscribe(QObject *o, int notifyIndex, int subIndex, QQmlEngine *);
inline static qint32 toInt32(double n);
static const double D32;
QQmlProperty prop(target(), propName);
if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
QQmlBoundSignal *signal =
- new QQmlBoundSignal(target(), prop.method(), this);
+ new QQmlBoundSignal(target(), prop.method(), this, qmlEngine(this));
QString location;
QQmlContextData *ctxtdata = 0;
--- /dev/null
+import Qt.test 1.0
+import QtQuick 2.0
+
+MyWorkerObject {
+ property bool passed: false
+ Component.onCompleted: doIt()
+ onDone: passed = (result == 'good')
+}
#include <QPlainTextEdit>
#include <QQmlEngine>
#include <QJSEngine>
+#include <QThread>
class BaseExtensionObject : public QObject
{
return o;
}
+class MyWorkerObjectThread : public QThread
+{
+public:
+ MyWorkerObjectThread(MyWorkerObject *o) : QThread(o), o(o) { start(); }
+
+ virtual void run() {
+ emit o->done(QLatin1String("good"));
+ }
+
+ MyWorkerObject *o;
+};
+
+void MyWorkerObject::doIt()
+{
+ new MyWorkerObjectThread(this);
+}
+
void registerTypes()
{
qmlRegisterType<MyQmlObject>("Qt.test", 1,0, "MyQmlObjectAlias");
qmlRegisterType<MyRevisionedClass>("Qt.test",1,0,"MyRevisionedClass");
qmlRegisterType<MyDeleteObject>("Qt.test", 1,0, "MyDeleteObject");
qmlRegisterType<MyRevisionedClass,1>("Qt.test",1,1,"MyRevisionedClass");
+ qmlRegisterType<MyWorkerObject>("Qt.test", 1,0, "MyWorkerObject");
// test scarce resource property binding post-evaluation optimisation
// and for testing memory usage in property var circular reference test
QString m_timespec;
};
+class MyWorkerObject : public QObject
+{
+ Q_OBJECT
+
+public Q_SLOTS:
+ void doIt();
+
+Q_SIGNALS:
+ void done(const QString &result);
+};
+
void registerTypes();
#endif // TESTTYPES_H
void deleteRootObjectInCreation();
void onDestruction();
void bindingSuppression();
-
void signalEmitted();
+ void threadSignal();
private:
static void propertyVarWeakRefCallback(v8::Persistent<v8::Value> object, void* parameter);
}
}
+// QTBUG-25647
+void tst_qqmlecmascript::threadSignal()
+{
+ QQmlComponent c(&engine, testFileUrl("threadSignal.qml"));
+ QObject *object = c.create();
+ QVERIFY(object != 0);
+ QTRY_VERIFY(object->property("passed").toBool());
+ delete object;
+}
+
QTEST_MAIN(tst_qqmlecmascript)
#include "tst_qqmlecmascript.moc"