Allow reference to signals using 'on' handler syntax.
This will allow APIs like the following: trigger: mouseArea.onClicked However, signal handlers will not be callable from QML: mouseArea.onClicked() //throws exception Change-Id: I2ef5cb4e1f3ed4814ef590962391e1b14e3f0c43 Reviewed-on: http://codereview.qt.nokia.com/3683 Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com> Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>
This commit is contained in:
parent
1dd8b50907
commit
d481f2ff51
|
@ -155,6 +155,7 @@ struct QMetaObjectPrivate
|
|||
int enumeratorCount, enumeratorData;
|
||||
int constructorCount, constructorData;
|
||||
int flags;
|
||||
int signalCount;
|
||||
};
|
||||
|
||||
static inline const QMetaObjectPrivate *priv(const uint* data)
|
||||
|
@ -1206,7 +1207,7 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf,
|
|||
QMetaObjectPrivate *pmeta
|
||||
= reinterpret_cast<QMetaObjectPrivate *>(buf + size);
|
||||
int pmetaSize = size;
|
||||
dataIndex = 13; // Number of fields in the QMetaObjectPrivate.
|
||||
dataIndex = 14; // Number of fields in the QMetaObjectPrivate.
|
||||
for (index = 0; index < d->properties.size(); ++index) {
|
||||
if (d->properties[index].notifySignal != -1) {
|
||||
hasNotifySignals = true;
|
||||
|
@ -1214,9 +1215,10 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf,
|
|||
}
|
||||
}
|
||||
if (buf) {
|
||||
pmeta->revision = 3;
|
||||
pmeta->revision = 4;
|
||||
pmeta->flags = d->flags;
|
||||
pmeta->className = 0; // Class name is always the first string.
|
||||
//pmeta->signalCount is handled in the "output method loop" as an optimization.
|
||||
|
||||
pmeta->classInfoCount = d->classInfoNames.size();
|
||||
pmeta->classInfoData = dataIndex;
|
||||
|
@ -1274,7 +1276,7 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf,
|
|||
}
|
||||
|
||||
// Reset the current data position to just past the QMetaObjectPrivate.
|
||||
dataIndex = 13;
|
||||
dataIndex = 14;
|
||||
|
||||
// Add the class name to the string table.
|
||||
int offset = 0;
|
||||
|
@ -1312,6 +1314,8 @@ static int buildMetaObject(QMetaObjectBuilderPrivate *d, char *buf,
|
|||
data[dataIndex + 2] = ret;
|
||||
data[dataIndex + 3] = tag;
|
||||
data[dataIndex + 4] = attrs;
|
||||
if (method->methodType() == QMetaMethod::Signal)
|
||||
pmeta->signalCount++;
|
||||
}
|
||||
dataIndex += 5;
|
||||
}
|
||||
|
|
|
@ -313,10 +313,14 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb
|
|||
allowedRevisionCache.append(0);
|
||||
|
||||
int methodCount = metaObject->methodCount();
|
||||
Q_ASSERT(QMetaObjectPrivate::get(metaObject)->revision >= 4);
|
||||
int signalCount = QMetaObjectPrivate::get(metaObject)->signalCount;
|
||||
// 3 to block the destroyed signal and the deleteLater() slot
|
||||
int methodOffset = qMax(3, metaObject->methodOffset());
|
||||
|
||||
methodIndexCache.resize(methodCount - methodIndexCacheStart);
|
||||
signalHandlerIndexCache.resize(signalCount);
|
||||
int signalHandlerIndex = 0;
|
||||
for (int ii = methodOffset; ii < methodCount; ++ii) {
|
||||
QMetaMethod m = metaObject->method(ii);
|
||||
if (m.access() == QMetaMethod::Private)
|
||||
|
@ -329,6 +333,7 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb
|
|||
while (*cptr != '(') { Q_ASSERT(*cptr != 0); utf8 |= *cptr & 0x80; ++cptr; }
|
||||
|
||||
Data *data = &methodIndexCache[ii - methodIndexCacheStart];
|
||||
Data *sigdata = 0;
|
||||
|
||||
data->lazyLoad(m);
|
||||
|
||||
|
@ -342,6 +347,12 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb
|
|||
|
||||
data->metaObjectOffset = allowedRevisionCache.count() - 1;
|
||||
|
||||
if (data->isSignal()) {
|
||||
sigdata = &signalHandlerIndexCache[signalHandlerIndex];
|
||||
*sigdata = *data;
|
||||
sigdata->flags |= Data::IsSignalHandler;
|
||||
}
|
||||
|
||||
Data *old = 0;
|
||||
|
||||
if (utf8) {
|
||||
|
@ -349,11 +360,33 @@ void QDeclarativePropertyCache::append(QDeclarativeEngine *engine, const QMetaOb
|
|||
if (Data **it = stringCache.value(methodName))
|
||||
old = *it;
|
||||
stringCache.insert(methodName, data);
|
||||
|
||||
if (data->isSignal()) {
|
||||
QHashedString on(QStringLiteral("on") % methodName.at(0).toUpper() % methodName.midRef(1));
|
||||
stringCache.insert(on, sigdata);
|
||||
++signalHandlerIndex;
|
||||
}
|
||||
} else {
|
||||
QHashedCStringRef methodName(signature, cptr - signature);
|
||||
if (Data **it = stringCache.value(methodName))
|
||||
old = *it;
|
||||
stringCache.insert(methodName, data);
|
||||
|
||||
if (data->isSignal()) {
|
||||
int length = methodName.length();
|
||||
|
||||
char str[length + 3];
|
||||
str[0] = 'o';
|
||||
str[1] = 'n';
|
||||
str[2] = toupper(signature[0]);
|
||||
if (length > 1)
|
||||
memcpy(&str[3], &signature[1], length - 1);
|
||||
str[length + 2] = '\0';
|
||||
|
||||
QHashedString on(QString::fromLatin1(str));
|
||||
stringCache.insert(on, sigdata);
|
||||
++signalHandlerIndex;
|
||||
}
|
||||
}
|
||||
|
||||
if (old) {
|
||||
|
|
|
@ -104,9 +104,10 @@ public:
|
|||
IsSignal = 0x00008000, // Function is a signal
|
||||
IsVMESignal = 0x00010000, // Signal was added by QML
|
||||
IsV8Function = 0x00020000, // Function takes QDeclarativeV8Function* args
|
||||
IsSignalHandler = 0x00040000, // Function is a signal handler
|
||||
|
||||
// Internal QDeclarativePropertyCache flags
|
||||
NotFullyResolved = 0x00040000 // True if the type data is to be lazily resolved
|
||||
NotFullyResolved = 0x00080000 // True if the type data is to be lazily resolved
|
||||
};
|
||||
Q_DECLARE_FLAGS(Flags, Flag)
|
||||
|
||||
|
@ -133,6 +134,7 @@ public:
|
|||
bool isSignal() const { return flags & IsSignal; }
|
||||
bool isVMESignal() const { return flags & IsVMESignal; }
|
||||
bool isV8Function() const { return flags & IsV8Function; }
|
||||
bool isSignalHandler() const { return flags & IsSignalHandler; }
|
||||
|
||||
union {
|
||||
int propType; // When !NotFullyResolved
|
||||
|
@ -221,6 +223,7 @@ private:
|
|||
|
||||
IndexCache propertyIndexCache;
|
||||
IndexCache methodIndexCache;
|
||||
IndexCache signalHandlerIndexCache;
|
||||
StringCache stringCache;
|
||||
AllowedRevisionCache allowedRevisionCache;
|
||||
v8::Persistent<v8::Function> constructor;
|
||||
|
|
|
@ -135,7 +135,7 @@ public:
|
|||
QV8ObjectResource(QV8Engine *engine) : engine(engine) { Q_ASSERT(engine); }
|
||||
enum ResourceType { ContextType, QObjectType, TypeType, ListType, VariantType,
|
||||
ValueTypeType, XMLHttpRequestType, DOMNodeType, SQLDatabaseType,
|
||||
ListModelType, Context2DType, ParticleDataType };
|
||||
ListModelType, Context2DType, ParticleDataType, SignalHandlerType };
|
||||
virtual ResourceType resourceType() const = 0;
|
||||
|
||||
QV8Engine *engine;
|
||||
|
|
|
@ -109,6 +109,16 @@ public:
|
|||
QV8QObjectWrapper *wrapper;
|
||||
};
|
||||
|
||||
class QV8SignalHandlerResource : public QV8ObjectResource
|
||||
{
|
||||
V8_RESOURCE_TYPE(SignalHandlerType)
|
||||
public:
|
||||
QV8SignalHandlerResource(QV8Engine *engine, QObject *object, int index);
|
||||
|
||||
QDeclarativeGuard<QObject> object;
|
||||
int index;
|
||||
};
|
||||
|
||||
namespace {
|
||||
struct MetaCallArgument {
|
||||
inline MetaCallArgument();
|
||||
|
@ -152,6 +162,11 @@ QV8QObjectResource::QV8QObjectResource(QV8Engine *engine, QObject *object)
|
|||
{
|
||||
}
|
||||
|
||||
QV8SignalHandlerResource::QV8SignalHandlerResource(QV8Engine *engine, QObject *object, int index)
|
||||
: QV8ObjectResource(engine), object(object), index(index)
|
||||
{
|
||||
}
|
||||
|
||||
static QAtomicInt objectIdCounter(1);
|
||||
|
||||
QV8QObjectWrapper::QV8QObjectWrapper()
|
||||
|
@ -177,6 +192,7 @@ void QV8QObjectWrapper::destroy()
|
|||
qPersistentDispose(m_hiddenObject);
|
||||
qPersistentDispose(m_destroySymbol);
|
||||
qPersistentDispose(m_toStringSymbol);
|
||||
qPersistentDispose(m_signalHandlerConstructor);
|
||||
qPersistentDispose(m_methodConstructor);
|
||||
qPersistentDispose(m_constructor);
|
||||
}
|
||||
|
@ -278,10 +294,21 @@ void QV8QObjectWrapper::init(QV8Engine *engine)
|
|||
m_methodConstructor = qPersistentNew<v8::Function>(createFn);
|
||||
}
|
||||
|
||||
v8::Local<v8::Function> connect = V8FUNCTION(Connect, engine);
|
||||
v8::Local<v8::Function> disconnect = V8FUNCTION(Disconnect, engine);
|
||||
|
||||
{
|
||||
v8::Local<v8::FunctionTemplate> ft = v8::FunctionTemplate::New();
|
||||
ft->InstanceTemplate()->SetHasExternalResource(true);
|
||||
ft->PrototypeTemplate()->Set(v8::String::New("connect"), connect, v8::DontEnum);
|
||||
ft->PrototypeTemplate()->Set(v8::String::New("disconnect"), disconnect, v8::DontEnum);
|
||||
m_signalHandlerConstructor = qPersistentNew<v8::Function>(ft->GetFunction());
|
||||
}
|
||||
|
||||
{
|
||||
v8::Local<v8::Object> prototype = engine->global()->Get(v8::String::New("Function"))->ToObject()->Get(v8::String::New("prototype"))->ToObject();
|
||||
prototype->Set(v8::String::New("connect"), V8FUNCTION(Connect, engine), v8::DontEnum);
|
||||
prototype->Set(v8::String::New("disconnect"), V8FUNCTION(Disconnect, engine), v8::DontEnum);
|
||||
prototype->Set(v8::String::New("connect"), connect, v8::DontEnum);
|
||||
prototype->Set(v8::String::New("disconnect"), disconnect, v8::DontEnum);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -461,6 +488,11 @@ v8::Handle<v8::Value> QV8QObjectWrapper::GetProperty(QV8Engine *engine, QObject
|
|||
return ((QDeclarativeVMEMetaObject *)(object->metaObject()))->vmeMethod(result->coreIndex);
|
||||
} else if (result->isV8Function()) {
|
||||
return MethodClosure::createWithGlobal(engine, object, objectHandle, result->coreIndex);
|
||||
} else if (result->isSignalHandler()) {
|
||||
v8::Local<v8::Object> handler = engine->qobjectWrapper()->m_signalHandlerConstructor->NewInstance();
|
||||
QV8SignalHandlerResource *r = new QV8SignalHandlerResource(engine, object, result->coreIndex);
|
||||
handler->SetExternalResource(r);
|
||||
return handler;
|
||||
} else {
|
||||
return MethodClosure::create(engine, object, objectHandle, result->coreIndex);
|
||||
}
|
||||
|
@ -998,6 +1030,17 @@ v8::Handle<v8::Value> QV8QObjectWrapper::newQObject(QObject *object)
|
|||
}
|
||||
}
|
||||
|
||||
QPair<QObject *, int> QV8QObjectWrapper::ExtractQtSignal(QV8Engine *engine, v8::Handle<v8::Object> object)
|
||||
{
|
||||
if (object->IsFunction())
|
||||
return ExtractQtMethod(engine, v8::Handle<v8::Function>::Cast(object));
|
||||
|
||||
if (QV8SignalHandlerResource *resource = v8_resource_cast<QV8SignalHandlerResource>(object))
|
||||
return qMakePair(resource->object.data(), resource->index);
|
||||
|
||||
return qMakePair((QObject *)0, -1);
|
||||
}
|
||||
|
||||
QPair<QObject *, int> QV8QObjectWrapper::ExtractQtMethod(QV8Engine *engine, v8::Handle<v8::Function> function)
|
||||
{
|
||||
v8::ScriptOrigin origin = function->GetScriptOrigin();
|
||||
|
@ -1166,10 +1209,7 @@ v8::Handle<v8::Value> QV8QObjectWrapper::Connect(const v8::Arguments &args)
|
|||
|
||||
QV8Engine *engine = V8ENGINE();
|
||||
|
||||
if (!args.This()->IsFunction())
|
||||
V8THROW_ERROR("Function.prototype.connect: this object is not a signal");
|
||||
|
||||
QPair<QObject *, int> signalInfo = ExtractQtMethod(engine, v8::Handle<v8::Function>::Cast(args.This()));
|
||||
QPair<QObject *, int> signalInfo = ExtractQtSignal(engine, args.This());
|
||||
QObject *signalObject = signalInfo.first;
|
||||
int signalIndex = signalInfo.second;
|
||||
|
||||
|
@ -1228,10 +1268,7 @@ v8::Handle<v8::Value> QV8QObjectWrapper::Disconnect(const v8::Arguments &args)
|
|||
|
||||
QV8Engine *engine = V8ENGINE();
|
||||
|
||||
if (!args.This()->IsFunction())
|
||||
V8THROW_ERROR("Function.prototype.disconnect: this object is not a signal");
|
||||
|
||||
QPair<QObject *, int> signalInfo = ExtractQtMethod(engine, v8::Handle<v8::Function>::Cast(args.This()));
|
||||
QPair<QObject *, int> signalInfo = ExtractQtSignal(engine, args.This());
|
||||
QObject *signalObject = signalInfo.first;
|
||||
int signalIndex = signalInfo.second;
|
||||
|
||||
|
|
|
@ -111,11 +111,13 @@ private:
|
|||
static v8::Handle<v8::Value> Disconnect(const v8::Arguments &args);
|
||||
static v8::Handle<v8::Value> Invoke(const v8::Arguments &args);
|
||||
static QPair<QObject *, int> ExtractQtMethod(QV8Engine *, v8::Handle<v8::Function>);
|
||||
static QPair<QObject *, int> ExtractQtSignal(QV8Engine *, v8::Handle<v8::Object>);
|
||||
|
||||
QV8Engine *m_engine;
|
||||
quint32 m_id;
|
||||
v8::Persistent<v8::Function> m_constructor;
|
||||
v8::Persistent<v8::Function> m_methodConstructor;
|
||||
v8::Persistent<v8::Function> m_signalHandlerConstructor;
|
||||
v8::Persistent<v8::String> m_toStringSymbol;
|
||||
v8::Persistent<v8::String> m_destroySymbol;
|
||||
QHashedV8String m_toStringString;
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
import Qt.test 1.0
|
||||
import QtQuick 2.0
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
property int count: 0
|
||||
signal testSignal
|
||||
onTestSignal: count++
|
||||
|
||||
property int funcCount: 0
|
||||
function testFunction() {
|
||||
funcCount++;
|
||||
}
|
||||
|
||||
//should increment count
|
||||
function testSignalCall() {
|
||||
testSignal()
|
||||
}
|
||||
|
||||
//should NOT increment count, and should throw an exception
|
||||
property string errorString
|
||||
function testSignalHandlerCall() {
|
||||
try {
|
||||
onTestSignal()
|
||||
} catch (error) {
|
||||
errorString = error.toString();
|
||||
}
|
||||
}
|
||||
|
||||
//should increment funcCount once
|
||||
function testSignalConnection() {
|
||||
testSignal.connect(testFunction)
|
||||
testSignal();
|
||||
testSignal.disconnect(testFunction)
|
||||
testSignal();
|
||||
}
|
||||
|
||||
//should increment funcCount once
|
||||
function testSignalHandlerConnection() {
|
||||
onTestSignal.connect(testFunction)
|
||||
testSignal();
|
||||
onTestSignal.disconnect(testFunction)
|
||||
testSignal();
|
||||
}
|
||||
|
||||
//should be defined
|
||||
property bool definedResult: false
|
||||
function testSignalDefined() {
|
||||
if (testSignal !== undefined)
|
||||
definedResult = true;
|
||||
}
|
||||
|
||||
//should be defined
|
||||
property bool definedHandlerResult: false
|
||||
function testSignalHandlerDefined() {
|
||||
if (onTestSignal !== undefined)
|
||||
definedHandlerResult = true;
|
||||
}
|
||||
}
|
|
@ -188,6 +188,7 @@ private slots:
|
|||
void realToInt();
|
||||
void dynamicString();
|
||||
void include();
|
||||
void signalHandlers();
|
||||
|
||||
void callQtInvokables();
|
||||
void invokableObjectArg();
|
||||
|
@ -3597,6 +3598,36 @@ void tst_qdeclarativeecmascript::include()
|
|||
}
|
||||
}
|
||||
|
||||
void tst_qdeclarativeecmascript::signalHandlers()
|
||||
{
|
||||
QDeclarativeComponent component(&engine, TEST_FILE("signalHandlers.qml"));
|
||||
QObject *o = component.create();
|
||||
QVERIFY(o != 0);
|
||||
|
||||
QVERIFY(o->property("count").toInt() == 0);
|
||||
QMetaObject::invokeMethod(o, "testSignalCall");
|
||||
QCOMPARE(o->property("count").toInt(), 1);
|
||||
|
||||
QMetaObject::invokeMethod(o, "testSignalHandlerCall");
|
||||
QCOMPARE(o->property("count").toInt(), 1);
|
||||
QCOMPARE(o->property("errorString").toString(), QLatin1String("TypeError: Property 'onTestSignal' of object [object Object] is not a function"));
|
||||
|
||||
QVERIFY(o->property("funcCount").toInt() == 0);
|
||||
QMetaObject::invokeMethod(o, "testSignalConnection");
|
||||
QCOMPARE(o->property("funcCount").toInt(), 1);
|
||||
|
||||
QMetaObject::invokeMethod(o, "testSignalHandlerConnection");
|
||||
QCOMPARE(o->property("funcCount").toInt(), 2);
|
||||
|
||||
QMetaObject::invokeMethod(o, "testSignalDefined");
|
||||
QCOMPARE(o->property("definedResult").toBool(), true);
|
||||
|
||||
QMetaObject::invokeMethod(o, "testSignalHandlerDefined");
|
||||
QCOMPARE(o->property("definedHandlerResult").toBool(), true);
|
||||
|
||||
delete o;
|
||||
}
|
||||
|
||||
void tst_qdeclarativeecmascript::qtbug_10696()
|
||||
{
|
||||
QDeclarativeComponent component(&engine, TEST_FILE("qtbug_10696.qml"));
|
||||
|
|
Loading…
Reference in New Issue