Skip to content

Commit

Permalink
Allow reference to signals using 'on' handler syntax.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Michael Brasser authored and Qt by Nokia committed Sep 1, 2011
1 parent 1dd8b50 commit d481f2f
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 15 deletions.
10 changes: 7 additions & 3 deletions src/declarative/qml/ftw/qmetaobjectbuilder.cpp
Expand Up @@ -155,6 +155,7 @@ struct QMetaObjectPrivate
int enumeratorCount, enumeratorData;
int constructorCount, constructorData;
int flags;
int signalCount;
};

static inline const QMetaObjectPrivate *priv(const uint* data)
Expand Down Expand Up @@ -1206,17 +1207,18 @@ 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;
break;
}
}
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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down
33 changes: 33 additions & 0 deletions src/declarative/qml/qdeclarativepropertycache.cpp
Expand Up @@ -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)
Expand All @@ -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);

Expand All @@ -342,18 +347,46 @@ 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) {
QHashedString methodName(QString::fromUtf8(signature, cptr - signature));
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) {
Expand Down
5 changes: 4 additions & 1 deletion src/declarative/qml/qdeclarativepropertycache_p.h
Expand Up @@ -104,9 +104,10 @@ class Q_DECLARATIVE_EXPORT QDeclarativePropertyCache : public QDeclarativeRefCou
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)

Expand All @@ -133,6 +134,7 @@ class Q_DECLARATIVE_EXPORT QDeclarativePropertyCache : public QDeclarativeRefCou
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
Expand Down Expand Up @@ -221,6 +223,7 @@ class Q_DECLARATIVE_EXPORT QDeclarativePropertyCache : public QDeclarativeRefCou

IndexCache propertyIndexCache;
IndexCache methodIndexCache;
IndexCache signalHandlerIndexCache;
StringCache stringCache;
AllowedRevisionCache allowedRevisionCache;
v8::Persistent<v8::Function> constructor;
Expand Down
2 changes: 1 addition & 1 deletion src/declarative/qml/v8/qv8engine_p.h
Expand Up @@ -135,7 +135,7 @@ class QV8ObjectResource : public v8::Object::ExternalResource
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;
Expand Down
57 changes: 47 additions & 10 deletions src/declarative/qml/v8/qv8qobjectwrapper.cpp
Expand Up @@ -109,6 +109,16 @@ class QV8QObjectInstance : public QDeclarativeGuard<QObject>
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();
Expand Down Expand Up @@ -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()
Expand All @@ -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);
}
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions src/declarative/qml/v8/qv8qobjectwrapper_p.h
Expand Up @@ -111,11 +111,13 @@ class Q_DECLARATIVE_EXPORT QV8QObjectWrapper
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;
Expand Down
@@ -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;
}
}
Expand Up @@ -188,6 +188,7 @@ private slots:
void realToInt();
void dynamicString();
void include();
void signalHandlers();

void callQtInvokables();
void invokableObjectArg();
Expand Down Expand Up @@ -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"));
Expand Down

0 comments on commit d481f2f

Please sign in to comment.