diff options
author | Thomas McGuire <thomas.mcguire.qnx@kdab.com> | 2012-08-06 07:36:05 (GMT) |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-08-31 13:08:29 (GMT) |
commit | 3c4c970962d75a902ff14a1c8b3adb36895c4ffa (patch) | |
tree | 6f6a4060c3e6b9179ac731398f7832427aa28fb1 | |
parent | 43669c58027b9e6ba76b6b2ca6059ab3d9e8c0ea (diff) | |
download | Qt-3c4c970962d75a902ff14a1c8b3adb36895c4ffa.zip Qt-3c4c970962d75a902ff14a1c8b3adb36895c4ffa.tar.gz Qt-3c4c970962d75a902ff14a1c8b3adb36895c4ffa.tar.bz2 |
Make connectNotify() work with QML
Call connectNotify() and disconnectNotify() in QML signal
handlers and in QML bindings.
This is a backport of Qt5's QtDeclarative commit
26ea8e01e9ee2a8c8c09266147b94c9ac92d09f9.
Task-number: QTBUG-11284
Change-Id: If5c3701426208875f3b775040c4e7bcbaac2b0a9
Reviewed-by: Kent Hansen <kent.hansen@nokia.com>
Reviewed-by: Lars Knoll <lars.knoll@nokia.com>
17 files changed, 643 insertions, 34 deletions
diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h index 3d9ea64..c7aa683 100644 --- a/src/corelib/kernel/qobject_p.h +++ b/src/corelib/kernel/qobject_p.h @@ -179,6 +179,11 @@ public: int signalIndex(const char *signalName) const; inline bool isSignalConnected(uint signalIdx) const; + // To allow arbitrary objects to call connectNotify()/disconnectNotify() without making + // the API public in QObject. This is used by QDeclarativeNotifierEndpoint. + inline void connectNotify(const char *signal); + inline void disconnectNotify(const char *signal); + public: QString objectName; ExtraData *extraData; // extra data set by the user @@ -229,6 +234,16 @@ inline bool QObjectPrivate::isSignalConnected(uint signal_index) const || qt_signal_spy_callback_set.signal_end_callback); } +inline void QObjectPrivate::connectNotify(const char *signal) +{ + q_ptr->connectNotify(signal); +} + +inline void QObjectPrivate::disconnectNotify(const char *signal) +{ + q_ptr->disconnectNotify(signal); +} + inline QObjectPrivate::Sender *QObjectPrivate::setCurrentSender(QObject *receiver, Sender *sender) { diff --git a/src/declarative/qml/qdeclarativebinding.cpp b/src/declarative/qml/qdeclarativebinding.cpp index 5acbec9..779fdd1 100644 --- a/src/declarative/qml/qdeclarativebinding.cpp +++ b/src/declarative/qml/qdeclarativebinding.cpp @@ -75,8 +75,11 @@ Bindings are free to implement their own memory management, so the delete operat necessarily safe. The default implementation clears the binding, removes it from the object and calls delete. */ -void QDeclarativeAbstractBinding::destroy() +void QDeclarativeAbstractBinding::destroy(DestroyMode mode) { + if (mode == DisconnectBinding) + disconnect(QDeclarativeAbstractBinding::DisconnectOne); + removeFromObject(); clear(); @@ -488,6 +491,12 @@ QString QDeclarativeBinding::expression() const return QDeclarativeExpression::expression(); } +void QDeclarativeBinding::disconnect(DisconnectMode disconnectMode) +{ + Q_UNUSED(disconnectMode); + setNotifyOnValueChanged(false); +} + QDeclarativeValueTypeProxyBinding::QDeclarativeValueTypeProxyBinding(QObject *o, int index) : m_object(o), m_index(index), m_bindings(0) { @@ -539,6 +548,12 @@ void QDeclarativeValueTypeProxyBinding::update(QDeclarativePropertyPrivate::Writ { } +void QDeclarativeValueTypeProxyBinding::disconnect(DisconnectMode disconnectMode) +{ + Q_UNUSED(disconnectMode); + // Nothing to do +} + QDeclarativeAbstractBinding *QDeclarativeValueTypeProxyBinding::binding(int propertyIndex) { QDeclarativeAbstractBinding *binding = m_bindings; diff --git a/src/declarative/qml/qdeclarativebinding_p.h b/src/declarative/qml/qdeclarativebinding_p.h index 01d5070..a6bde99 100644 --- a/src/declarative/qml/qdeclarativebinding_p.h +++ b/src/declarative/qml/qdeclarativebinding_p.h @@ -71,10 +71,39 @@ public: QDeclarativeAbstractBinding(); - virtual void destroy(); + enum DestroyMode { + // The binding should disconnect itself upon destroy + DisconnectBinding, + + // The binding doesn't need to disconnect itself, but it can if it wants to. + // + // This is used in QDeclarativeData::destroyed() - at the point at which the bindings are + // destroyed, the notifiers are already disconnected, so no need to disconnect each + // binding again. + // + // Bindings can use this flag to speed up destruction, especially for compiled bindings + // disconnecting a single binding might be slow. + KeepBindingConnected + }; + + virtual void destroy(DestroyMode mode = DisconnectBinding); virtual QString expression() const; + enum DisconnectMode { + + // Just this single binding is getting disconnected, other bindings remain connected and + // should not be changed. + DisconnectOne, + + // All bindings of the same object are getting disconnected. As an optimization, it is + // therefore valid to disconnect all bindings in one go. + DisconnectAll + }; + + // disconnectMode can be ignored, it is just a hint for potential optimization + virtual void disconnect(DisconnectMode disconnectMode) = 0; + enum Type { PropertyBinding, ValueTypeProxy }; virtual Type bindingType() const { return PropertyBinding; } @@ -123,6 +152,7 @@ public: virtual void setEnabled(bool, QDeclarativePropertyPrivate::WriteFlags); virtual void update(QDeclarativePropertyPrivate::WriteFlags); + virtual void disconnect(DisconnectMode disconnectMode); QDeclarativeAbstractBinding *binding(int propertyIndex); @@ -168,6 +198,7 @@ public: virtual void setEnabled(bool, QDeclarativePropertyPrivate::WriteFlags flags); virtual void update(QDeclarativePropertyPrivate::WriteFlags flags); virtual QString expression() const; + virtual void disconnect(DisconnectMode disconnectMode); typedef int Identifier; static Identifier Invalid; diff --git a/src/declarative/qml/qdeclarativeboundsignal.cpp b/src/declarative/qml/qdeclarativeboundsignal.cpp index 089449e..c6ab1f1 100644 --- a/src/declarative/qml/qdeclarativeboundsignal.cpp +++ b/src/declarative/qml/qdeclarativeboundsignal.cpp @@ -97,21 +97,23 @@ QDeclarativeAbstractBoundSignal::~QDeclarativeAbstractBoundSignal() QDeclarativeBoundSignal::QDeclarativeBoundSignal(QObject *scope, const QMetaMethod &signal, QObject *parent) -: m_expression(0), m_signal(signal), m_paramsValid(false), m_isEvaluating(false), m_params(0) +: m_expression(0), m_signal(signal), m_paramsValid(false), m_isEvaluating(false), m_params(0), + m_scope(scope, this) { - // This is thread safe. Although it may be updated by two threads, they - // will both set it to the same value - so the worst thing that can happen - // is that they both do the work to figure it out. Boo hoo. - if (evaluateIdx == -1) evaluateIdx = metaObject()->methodCount(); - - QDeclarative_setParent_noEvent(this, parent); - QDeclarativePropertyPrivate::connect(scope, m_signal.methodIndex(), this, evaluateIdx); + init(parent); } QDeclarativeBoundSignal::QDeclarativeBoundSignal(QDeclarativeContext *ctxt, const QString &val, QObject *scope, const QMetaMethod &signal, QObject *parent) -: m_expression(0), m_signal(signal), m_paramsValid(false), m_isEvaluating(false), m_params(0) +: m_expression(0), m_signal(signal), m_paramsValid(false), m_isEvaluating(false), m_params(0), + m_scope(scope, this) +{ + init(parent); + m_expression = new QDeclarativeExpression(ctxt, scope, val); +} + +void QDeclarativeBoundSignal::init(QObject *parent) { // This is thread safe. Although it may be updated by two threads, they // will both set it to the same value - so the worst thing that can happen @@ -119,17 +121,25 @@ QDeclarativeBoundSignal::QDeclarativeBoundSignal(QDeclarativeContext *ctxt, cons if (evaluateIdx == -1) evaluateIdx = metaObject()->methodCount(); QDeclarative_setParent_noEvent(this, parent); - QDeclarativePropertyPrivate::connect(scope, m_signal.methodIndex(), this, evaluateIdx); + QDeclarativePropertyPrivate::connect(m_scope, m_signal.methodIndex(), this, evaluateIdx); - m_expression = new QDeclarativeExpression(ctxt, scope, val); + QDeclarativeData * const data = QDeclarativeData::get(m_scope, true); + data->addBoundSignal(this); } QDeclarativeBoundSignal::~QDeclarativeBoundSignal() { + unregisterScopeObject(); delete m_expression; m_expression = 0; } +void QDeclarativeBoundSignal::disconnect() +{ + QObjectPrivate * const priv = QObjectPrivate::get(m_scope); + priv->disconnectNotify(m_signal.signature()); +} + int QDeclarativeBoundSignal::index() const { return m_signal.methodIndex(); @@ -196,6 +206,15 @@ int QDeclarativeBoundSignal::qt_metacall(QMetaObject::Call c, int id, void **a) } } +void QDeclarativeBoundSignal::unregisterScopeObject() +{ + if (m_scope) { + QDeclarativeData * const data = QDeclarativeData::get(m_scope, false); + if (data) + data->removeBoundSignal(this); + } +} + QDeclarativeBoundSignalParameters::QDeclarativeBoundSignalParameters(const QMetaMethod &method, QObject *parent) : QObject(parent), types(0), values(0) diff --git a/src/declarative/qml/qdeclarativeboundsignal_p.h b/src/declarative/qml/qdeclarativeboundsignal_p.h index a0dfe18..4ba8592 100644 --- a/src/declarative/qml/qdeclarativeboundsignal_p.h +++ b/src/declarative/qml/qdeclarativeboundsignal_p.h @@ -54,6 +54,7 @@ // #include "qdeclarativeexpression.h" +#include "qdeclarativeguard_p.h" #include <QtCore/qmetaobject.h> @@ -67,6 +68,10 @@ class QDeclarativeAbstractBoundSignal : public QObject public: QDeclarativeAbstractBoundSignal(QObject *parent = 0); virtual ~QDeclarativeAbstractBoundSignal() = 0; + virtual void disconnect() = 0; + +protected slots: + virtual void unregisterScopeObject() = 0; }; class QDeclarativeBoundSignalParameters; @@ -78,6 +83,8 @@ public: const QMetaMethod &signal, QObject *parent); virtual ~QDeclarativeBoundSignal(); + void disconnect(); + int index() const; QDeclarativeExpression *expression() const; @@ -88,14 +95,35 @@ public: static QDeclarativeBoundSignal *cast(QObject *); protected: + void unregisterScopeObject(); virtual int qt_metacall(QMetaObject::Call c, int id, void **a); private: + class ScopeGuard : public QDeclarativeGuard<QObject> + { + public: + ScopeGuard(QObject *object, QDeclarativeBoundSignal *signal) + : QDeclarativeGuard<QObject>(object), m_signal(signal) + { + } + + void objectDestroyed(QObject *obj) { + Q_UNUSED(obj); + m_signal->unregisterScopeObject(); + } + + private: + QDeclarativeBoundSignal *m_signal; + }; + + void init(QObject *parent); + QDeclarativeExpression *m_expression; QMetaMethod m_signal; bool m_paramsValid : 1; bool m_isEvaluating : 1; QDeclarativeBoundSignalParameters *m_params; + ScopeGuard m_scope; }; QT_END_NAMESPACE diff --git a/src/declarative/qml/qdeclarativecompiledbindings.cpp b/src/declarative/qml/qdeclarativecompiledbindings.cpp index 3ee365a..5aefa4a 100644 --- a/src/declarative/qml/qdeclarativecompiledbindings.cpp +++ b/src/declarative/qml/qdeclarativecompiledbindings.cpp @@ -216,7 +216,8 @@ public: // Inherited from QDeclarativeAbstractBinding virtual void setEnabled(bool, QDeclarativePropertyPrivate::WriteFlags flags); virtual void update(QDeclarativePropertyPrivate::WriteFlags flags); - virtual void destroy(); + virtual void destroy(DestroyMode mode); + virtual void disconnect(DisconnectMode disconnectMode); int index:30; bool enabled:1; @@ -238,6 +239,7 @@ public: QDeclarativeRefCount *dataRef; Binding *m_bindings; quint32 *m_signalTable; + bool m_bindingsDisconnected; static int methodCount; @@ -246,9 +248,10 @@ public: QDeclarativeDelayedError *error, QObject *scope, QObject *output, QDeclarativePropertyPrivate::WriteFlags storeFlags); - inline void unsubscribe(int subIndex); inline void subscribeId(QDeclarativeContextData *p, int idIndex, int subIndex); inline void subscribe(QObject *o, int notifyIndex, int subIndex); + inline void disconnectAll(); + inline void disconnectOne(Binding *bindingToDisconnect); QDeclarativePropertyCache::Data *findproperty(QObject *obj, const QScriptDeclarativeClass::Identifier &name, @@ -268,7 +271,8 @@ public: }; QDeclarativeCompiledBindingsPrivate::QDeclarativeCompiledBindingsPrivate() -: subscriptions(0), identifiers(0), programData(0), dataRef(0), m_bindings(0), m_signalTable(0) + : subscriptions(0), identifiers(0), programData(0), dataRef(0), m_bindings(0), m_signalTable(0), + m_bindingsDisconnected(false) { } @@ -343,14 +347,25 @@ void QDeclarativeCompiledBindingsPrivate::Binding::update(QDeclarativePropertyPr QDeclarativeDebugTrace::endRange(QDeclarativeDebugTrace::Binding); } -void QDeclarativeCompiledBindingsPrivate::Binding::destroy() +void QDeclarativeCompiledBindingsPrivate::Binding::destroy(DestroyMode mode) { + if (mode == DisconnectBinding) + disconnect(QDeclarativeAbstractBinding::DisconnectOne); + enabled = false; removeFromObject(); clear(); parent->q_func()->release(); } +void QDeclarativeCompiledBindingsPrivate::Binding::disconnect(DisconnectMode disconnectMode) +{ + if (disconnectMode == QDeclarativeAbstractBinding::DisconnectAll) + parent->disconnectAll(); + else + parent->disconnectOne(this); +} + int QDeclarativeCompiledBindings::qt_metacall(QMetaObject::Call c, int id, void **) { Q_D(QDeclarativeCompiledBindings); @@ -696,26 +711,20 @@ struct QDeclarativeBindingCompilerPrivate QByteArray buildExceptionData() const; }; -void QDeclarativeCompiledBindingsPrivate::unsubscribe(int subIndex) -{ - QDeclarativeCompiledBindingsPrivate::Subscription *sub = (subscriptions + subIndex); - sub->disconnect(); -} - void QDeclarativeCompiledBindingsPrivate::subscribeId(QDeclarativeContextData *p, int idIndex, int subIndex) { Q_Q(QDeclarativeCompiledBindings); - unsubscribe(subIndex); + QDeclarativeCompiledBindingsPrivate::Subscription *sub = (subscriptions + subIndex); + sub->disconnect(); if (p->idValues[idIndex]) { - QDeclarativeCompiledBindingsPrivate::Subscription *sub = (subscriptions + subIndex); sub->target = q; sub->targetMethod = methodCount + subIndex; sub->connect(&p->idValues[idIndex].bindings); } } - + void QDeclarativeCompiledBindingsPrivate::subscribe(QObject *o, int notifyIndex, int subIndex) { Q_Q(QDeclarativeCompiledBindings); @@ -729,6 +738,43 @@ void QDeclarativeCompiledBindingsPrivate::subscribe(QObject *o, int notifyIndex, sub->disconnect(); } +void QDeclarativeCompiledBindingsPrivate::disconnectAll() +{ + // This gets called multiple times in QDeclarativeData::disconnectNotifiers(), avoid unneeded + // work for all but the first call. + if (m_bindingsDisconnected) + return; + + // We disconnect all subscriptions, so we can call disconnect() unconditionally if there is at + // least one connection + Program *program = (Program *)programData; + for (int subIndex = 0; subIndex < program->subscriptions; ++subIndex) { + Subscription * const sub = (subscriptions + subIndex); + if (sub->isConnected()) + sub->disconnect(); + } + m_bindingsDisconnected = true; +} + +void QDeclarativeCompiledBindingsPrivate::disconnectOne( + QDeclarativeCompiledBindingsPrivate::Binding *bindingToDisconnect) +{ + // We iterate over the signal table to find all subscriptions for this binding. This is slowish, + // but disconnectOne() is only called when overwriting a binding, which is quite rare. + Program *program = (Program *)programData; + for (int subIndex = 0; subIndex < program->subscriptions; ++subIndex) { + Subscription * const sub = (subscriptions + subIndex); + quint32 *reeval = m_signalTable + m_signalTable[subIndex]; + quint32 bindingCount = *reeval; + ++reeval; + for (quint32 bindingIndex = 0; bindingIndex < bindingCount; ++bindingIndex) { + Binding * const binding = m_bindings + reeval[bindingIndex]; + if (binding == bindingToDisconnect) + sub->deref(); + } + } +} + // Conversion functions - these MUST match the QtScript expression path inline static qreal toReal(Register *reg, int type, bool *ok = 0) { diff --git a/src/declarative/qml/qdeclarativedata_p.h b/src/declarative/qml/qdeclarativedata_p.h index 7d71406..0057840 100644 --- a/src/declarative/qml/qdeclarativedata_p.h +++ b/src/declarative/qml/qdeclarativedata_p.h @@ -61,6 +61,7 @@ QT_BEGIN_NAMESPACE class QDeclarativeGuardImpl; class QDeclarativeCompiledData; class QDeclarativeAbstractBinding; +class QDeclarativeAbstractBoundSignal; class QDeclarativeContext; class QDeclarativePropertyCache; class QDeclarativeContextData; @@ -154,9 +155,12 @@ public: bool hasExtendedData() const { return extendedData != 0; } QDeclarativeNotifier *objectNameNotifier() const; QHash<int, QObject *> *attachedProperties() const; + void addBoundSignal(QDeclarativeAbstractBoundSignal *signal); + void removeBoundSignal(QDeclarativeAbstractBoundSignal *signal); + void disconnectNotifiers(); private: - // For objectNameNotifier and attachedProperties + // For objectNameNotifier, attachedProperties and bound signal list mutable QDeclarativeDataExtended *extendedData; }; diff --git a/src/declarative/qml/qdeclarativeengine.cpp b/src/declarative/qml/qdeclarativeengine.cpp index a787fb6..92a7391 100644 --- a/src/declarative/qml/qdeclarativeengine.cpp +++ b/src/declarative/qml/qdeclarativeengine.cpp @@ -42,6 +42,7 @@ #include "private/qdeclarativeengine_p.h" #include "qdeclarativeengine.h" +#include "private/qdeclarativeboundsignal_p.h" #include "private/qdeclarativecontext_p.h" #include "private/qdeclarativecompiler_p.h" #include "private/qdeclarativeglobalscriptclass_p.h" @@ -547,6 +548,11 @@ void QDeclarativePrivate::qdeclarativeelement_destructor(QObject *o) d->context->destroy(); d->context = 0; } + + // Disconnect the notifiers now - during object destruction this would be too late, since + // the disconnect call wouldn't be able to call disconnectNotify(), as it isn't possible to + // get the metaobject anymore. + d->disconnectNotifiers(); } } @@ -1108,6 +1114,7 @@ public: QHash<int, QObject *> attachedProperties; QDeclarativeNotifier objectNameNotifier; + QList<QDeclarativeAbstractBoundSignal *> boundSignals; }; QDeclarativeDataExtended::QDeclarativeDataExtended() @@ -1130,6 +1137,32 @@ QHash<int, QObject *> *QDeclarativeData::attachedProperties() const return &extendedData->attachedProperties; } +void QDeclarativeData::addBoundSignal(QDeclarativeAbstractBoundSignal *signal) +{ + if (!extendedData) extendedData = new QDeclarativeDataExtended; + extendedData->boundSignals.append(signal); +} + +void QDeclarativeData::removeBoundSignal(QDeclarativeAbstractBoundSignal *signal) +{ + if (extendedData) + extendedData->boundSignals.removeAll(signal); +} + +void QDeclarativeData::disconnectNotifiers() +{ + QDeclarativeAbstractBinding *binding = bindings; + while (binding) { + binding->disconnect(QDeclarativeAbstractBinding::DisconnectAll); + binding = binding->m_nextBinding; + } + + if (extendedData) { + Q_FOREACH (QDeclarativeAbstractBoundSignal *signal, extendedData->boundSignals) + signal->disconnect(); + } +} + void QDeclarativeData::destroyed(QObject *object) { if (deferredComponent) @@ -1145,7 +1178,7 @@ void QDeclarativeData::destroyed(QObject *object) QDeclarativeAbstractBinding *next = binding->m_nextBinding; binding->m_prevBinding = 0; binding->m_nextBinding = 0; - binding->destroy(); + binding->destroy(QDeclarativeAbstractBinding::KeepBindingConnected); binding = next; } diff --git a/src/declarative/qml/qdeclarativenotifier.cpp b/src/declarative/qml/qdeclarativenotifier.cpp index b625038..a3fae93 100644 --- a/src/declarative/qml/qdeclarativenotifier.cpp +++ b/src/declarative/qml/qdeclarativenotifier.cpp @@ -71,8 +71,10 @@ void QDeclarativeNotifierEndpoint::connect(QObject *source, int sourceSignal) { Signal *s = toSignal(); - if (s->source == source && s->sourceSignal == sourceSignal) + if (s->source == source && s->sourceSignal == sourceSignal) { + refCount++; return; + } disconnect(); @@ -80,6 +82,7 @@ void QDeclarativeNotifierEndpoint::connect(QObject *source, int sourceSignal) s->source = source; s->sourceSignal = sourceSignal; + refCount++; } void QDeclarativeNotifierEndpoint::copyAndClear(QDeclarativeNotifierEndpoint &other) diff --git a/src/declarative/qml/qdeclarativenotifier_p.h b/src/declarative/qml/qdeclarativenotifier_p.h index 524a966..ee80c9a 100644 --- a/src/declarative/qml/qdeclarativenotifier_p.h +++ b/src/declarative/qml/qdeclarativenotifier_p.h @@ -43,6 +43,7 @@ #define QDECLARATIVENOTIFIER_P_H #include "private/qdeclarativeguard_p.h" +#include <QtCore/qmetaobject.h> QT_BEGIN_NAMESPACE @@ -77,8 +78,13 @@ public: void connect(QObject *source, int sourceSignal); inline void connect(QDeclarativeNotifier *); + + // Disconnects unconditionally, regardless of the refcount inline void disconnect(); + // Decreases the refcount and disconnects when refcount reaches 0 + inline void deref(); + void copyAndClear(QDeclarativeNotifierEndpoint &other); private: @@ -110,6 +116,8 @@ private: Notifier notifier; }; + quint16 refCount; + inline Notifier *toNotifier(); inline Notifier *asNotifier(); inline Signal *toSignal(); @@ -143,12 +151,12 @@ void QDeclarativeNotifier::notify() } QDeclarativeNotifierEndpoint::QDeclarativeNotifierEndpoint() -: target(0), targetMethod(0), type(InvalidType) + : target(0), targetMethod(0), type(InvalidType), refCount(0) { } QDeclarativeNotifierEndpoint::QDeclarativeNotifierEndpoint(QObject *t, int m) -: target(t), targetMethod(m), type(InvalidType) +: target(t), targetMethod(m), type(InvalidType), refCount(0) { } @@ -186,8 +194,10 @@ void QDeclarativeNotifierEndpoint::connect(QDeclarativeNotifier *notifier) { Notifier *n = toNotifier(); - if (n->notifier == notifier) + if (n->notifier == notifier) { + refCount++; return; + } disconnect(); @@ -196,6 +206,7 @@ void QDeclarativeNotifierEndpoint::connect(QDeclarativeNotifier *notifier) notifier->endpoints = this; n->prev = ¬ifier->endpoints; n->notifier = notifier; + refCount++; } void QDeclarativeNotifierEndpoint::disconnect() @@ -204,6 +215,9 @@ void QDeclarativeNotifierEndpoint::disconnect() Signal *s = asSignal(); if (s->source) { QMetaObject::disconnectOne(s->source, s->sourceSignal, target, targetMethod); + QObjectPrivate * const priv = QObjectPrivate::get(s->source); + const QMetaMethod signal = s->source->metaObject()->method(s->sourceSignal); + priv->disconnectNotify(signal.signature()); s->source = 0; } } else if (type == NotifierType) { @@ -217,6 +231,14 @@ void QDeclarativeNotifierEndpoint::disconnect() n->disconnected = 0; n->notifier = 0; } + refCount = 0; +} + +void QDeclarativeNotifierEndpoint::deref() +{ + refCount--; + if (refCount <= 0) + disconnect(); } QDeclarativeNotifierEndpoint::Notifier *QDeclarativeNotifierEndpoint::toNotifier() diff --git a/src/declarative/qml/qdeclarativeproperty.cpp b/src/declarative/qml/qdeclarativeproperty.cpp index 84f492d..2867d27 100644 --- a/src/declarative/qml/qdeclarativeproperty.cpp +++ b/src/declarative/qml/qdeclarativeproperty.cpp @@ -1626,13 +1626,17 @@ it connects any lazy "proxy" signal connections set up by QML. It is possible that this logic should be moved to QMetaObject::connect(). */ -bool QDeclarativePropertyPrivate::connect(const QObject *sender, int signal_index, +bool QDeclarativePropertyPrivate::connect(QObject *sender, int signal_index, const QObject *receiver, int method_index, int type, int *types) { flush_vme_signal(sender, signal_index); flush_vme_signal(receiver, method_index); + const QMetaMethod signal = sender->metaObject()->method(signal_index); + QObjectPrivate * const senderPriv = QObjectPrivate::get(sender); + senderPriv->connectNotify(signal.signature()); + return QMetaObject::connect(sender, signal_index, receiver, method_index, type, types); } diff --git a/src/declarative/qml/qdeclarativeproperty_p.h b/src/declarative/qml/qdeclarativeproperty_p.h index 21e3341..4847445 100644 --- a/src/declarative/qml/qdeclarativeproperty_p.h +++ b/src/declarative/qml/qdeclarativeproperty_p.h @@ -134,7 +134,7 @@ public: static int valueTypeCoreIndex(const QDeclarativeProperty &that); static int bindingIndex(const QDeclarativeProperty &that); static QMetaMethod findSignalByName(const QMetaObject *mo, const QByteArray &); - static bool connect(const QObject *sender, int signal_index, + static bool connect(QObject *sender, int signal_index, const QObject *receiver, int method_index, int type = 0, int *types = 0); static const QMetaObject *metaObjectForProperty(const QMetaObject *, int); diff --git a/tests/auto/declarative/declarative.pro b/tests/auto/declarative/declarative.pro index 08d59d3..c9486b0 100644 --- a/tests/auto/declarative/declarative.pro +++ b/tests/auto/declarative/declarative.pro @@ -17,6 +17,7 @@ SUBDIRS += \ qdeclarativelayoutitem \ qdeclarativelistreference \ qdeclarativemoduleplugin \ + qdeclarativenotifier \ qdeclarativeparticles \ qdeclarativepixmapcache \ qdeclarativeqt \ diff --git a/tests/auto/declarative/qdeclarativenotifier/data/Base.qml b/tests/auto/declarative/qdeclarativenotifier/data/Base.qml new file mode 100644 index 0000000..1335b9d --- /dev/null +++ b/tests/auto/declarative/qdeclarativenotifier/data/Base.qml @@ -0,0 +1,5 @@ +import QtQuick 1.0 + +Item { + property int normalBinding +} diff --git a/tests/auto/declarative/qdeclarativenotifier/data/connectnotify.qml b/tests/auto/declarative/qdeclarativenotifier/data/connectnotify.qml new file mode 100644 index 0000000..5412f33 --- /dev/null +++ b/tests/auto/declarative/qdeclarativenotifier/data/connectnotify.qml @@ -0,0 +1,69 @@ +import QtQuick 1.0 +import Test 1.0 + +Base { + id: root + ExportedClass { + id: exportedClass + objectName: "exportedClass" + onBoundSignal: {} + property int buw: selfProp + } + + property int compiledBinding: exportedClass.compiledBindingProp + + normalBinding: { + Math.abs(12); // Prevent optimization to a compiled binding + return exportedClass.normalBindingProp + } + + property int foo: exportedClass.qmlObjectProp + property int baz: _exportedObject.cppObjectProp + + // Compiled bindings that share a subscription. + property int compiledBindingShared_1: exportedClass.compiledBindingPropShared + property int compiledBindingShared_2: exportedClass.compiledBindingPropShared + + function removeCompiledBinding() { + //console.log("Going to remove compiled binding...") + root.compiledBinding = 1; + //console.log("Binding removed!") + } + + function removeNormalBinding() { + //console.log("Going to remove normal binding...") + root.normalBinding = 1; + //console.log("Binding removed!") + } + + function removeCompiledBindingShared_1() { + //console.log("Going to remove compiled binding shared 1...") + root.compiledBindingShared_1 = 1; + //console.log("Binding removed!") + } + + function removeCompiledBindingShared_2() { + //console.log("Going to remove compiled binding shared 2...") + root.compiledBindingShared_2 = 1; + //console.log("Binding removed!") + } + + function readProperty() { + var test = exportedClass.unboundProp + } + + + function changeState() { + //console.log("Changing state...") + if (root.state == "") root.state = "state1" + else root.state = "" + //console.log("State changed.") + } + + property int someValue: 42 + + states: State { + name: "state1" + PropertyChanges { target: root; someValue: exportedClass.unboundProp } + } +} diff --git a/tests/auto/declarative/qdeclarativenotifier/qdeclarativenotifier.pro b/tests/auto/declarative/qdeclarativenotifier/qdeclarativenotifier.pro new file mode 100644 index 0000000..e7f5b19 --- /dev/null +++ b/tests/auto/declarative/qdeclarativenotifier/qdeclarativenotifier.pro @@ -0,0 +1,12 @@ +load(qttest_p4) +contains(QT_CONFIG,declarative): QT += declarative +SOURCES += tst_qdeclarativenotifier.cpp +macx:CONFIG -= app_bundle + +wince*: { + DEFINES += SRCDIR=\\\".\\\" +} else:!symbian: { + DEFINES += SRCDIR=\\\"$$PWD\\\" +} + +CONFIG += parallel_test diff --git a/tests/auto/declarative/qdeclarativenotifier/tst_qdeclarativenotifier.cpp b/tests/auto/declarative/qdeclarativenotifier/tst_qdeclarativenotifier.cpp new file mode 100644 index 0000000..9fb028b --- /dev/null +++ b/tests/auto/declarative/qdeclarativenotifier/tst_qdeclarativenotifier.cpp @@ -0,0 +1,302 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Research In Motion +** Contact: http://www.qt-project.org/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <qtest.h> +#include <QDebug> +#include <QDeclarativeEngine> +#include <QDeclarativeComponent> +#include <QDeclarativeContext> +#include <qdeclarative.h> +#include <QMetaMethod> + +#ifdef Q_OS_SYMBIAN +// In Symbian OS test data is located in applications private dir +#define SRCDIR "." +#endif + + +class ExportedClass : public QObject +{ + Q_OBJECT + Q_PROPERTY(int selfProp READ selfProp NOTIFY selfPropChanged) + Q_PROPERTY(int qmlObjectProp READ qmlObjectProp NOTIFY qmlObjectPropChanged) + Q_PROPERTY(int cppObjectProp READ cppObjectProp NOTIFY cppObjectPropChanged) + Q_PROPERTY(int unboundProp READ unboundProp NOTIFY unboundPropChanged) + Q_PROPERTY(int normalBindingProp READ normalBindingProp NOTIFY normalBindingPropChanged) + Q_PROPERTY(int compiledBindingProp READ compiledBindingProp NOTIFY compiledBindingPropChanged) + Q_PROPERTY(int compiledBindingPropShared READ compiledBindingPropShared NOTIFY compiledBindingPropSharedChanged) +public: + int selfPropConnections; + int qmlObjectPropConnections; + int cppObjectPropConnections; + int unboundPropConnections; + int normalBindingPropConnections; + int compiledBindingPropConnections; + int compiledBindingPropSharedConnections; + int boundSignalConnections; + int unusedSignalConnections; + + ExportedClass() + : selfPropConnections(0), qmlObjectPropConnections(0), cppObjectPropConnections(0), + unboundPropConnections(0), normalBindingPropConnections(0), compiledBindingPropConnections(0), + compiledBindingPropSharedConnections(0), boundSignalConnections(0), + unusedSignalConnections(0) + {} + + ~ExportedClass() + { + QCOMPARE(selfPropConnections, 0); + QCOMPARE(qmlObjectPropConnections, 0); + QCOMPARE(cppObjectPropConnections, 0); + QCOMPARE(unboundPropConnections, 0); + QCOMPARE(normalBindingPropConnections, 0); + QCOMPARE(compiledBindingPropConnections, 0); + QCOMPARE(compiledBindingPropSharedConnections, 0); + QCOMPARE(boundSignalConnections, 0); + QCOMPARE(unusedSignalConnections, 0); + } + + int selfProp() const { return 42; } + int qmlObjectProp() const { return 42; } + int unboundProp() const { return 42; } + int normalBindingProp() const { return 42; } + int compiledBindingProp() const { return 42; } + int compiledBindingPropShared() const { return 42; } + int cppObjectProp() const { return 42; } +protected: + void connectNotify(const char *signal) + { + const QString signalName(signal); + if (signalName == "selfPropChanged()") selfPropConnections++; + if (signalName == "qmlObjectPropChanged()") qmlObjectPropConnections++; + if (signalName == "cppObjectPropChanged()") cppObjectPropConnections++; + if (signalName == "unboundPropChanged()") unboundPropConnections++; + if (signalName == "normalBindingPropChanged()") normalBindingPropConnections++; + if (signalName == "compiledBindingPropChanged()") compiledBindingPropConnections++; + if (signalName == "compiledBindingPropSharedChanged()") compiledBindingPropSharedConnections++; + if (signalName == "boundSignal()") boundSignalConnections++; + if (signalName == "unusedSignal()") unusedSignalConnections++; + //qDebug() << Q_FUNC_INFO << this << signalName; + } + + void disconnectNotify(const char *signal) + { + const QString signalName(signal); + if (signalName == "selfPropChanged()") selfPropConnections--; + if (signalName == "qmlObjectPropChanged()") qmlObjectPropConnections--; + if (signalName == "cppObjectPropChanged()") cppObjectPropConnections--; + if (signalName == "unboundPropChanged()") unboundPropConnections--; + if (signalName == "normalBindingPropChanged()") normalBindingPropConnections--; + if (signalName == "compiledBindingPropChanged()") compiledBindingPropConnections--; + if (signalName == "compiledBindingPropSharedChanged()") compiledBindingPropSharedConnections--; + if (signalName == "boundSignal()") boundSignalConnections--; + if (signalName == "unusedSignal()") unusedSignalConnections--; + //qDebug() << Q_FUNC_INFO << this << signalName; + } + +signals: + void selfPropChanged(); + void qmlObjectPropChanged(); + void cppObjectPropChanged(); + void unboundPropChanged(); + void normalBindingPropChanged(); + void compiledBindingPropChanged(); + void compiledBindingPropSharedChanged(); + void boundSignal(); + void unusedSignal(); +}; + +class tst_qdeclarativenotifier : public QObject +{ + Q_OBJECT +public: + tst_qdeclarativenotifier() + : root(0), exportedClass(0), exportedObject(0) + {} + +private slots: + void initTestCase(); + void cleanupTestCase(); + void connectNotify(); + + void removeCompiledBinding(); + void removeCompiledBindingShared(); + void removeNormalBinding(); + // No need to test value type proxy bindings - the user can't override disconnectNotify() anyway, + // as the classes are private to the QML engine + + void readProperty(); + void propertyChange(); + void disconnectOnDestroy(); + +private: + void createObjects(); + + QDeclarativeEngine engine; + QObject *root; + ExportedClass *exportedClass; + ExportedClass *exportedObject; +}; + +void tst_qdeclarativenotifier::initTestCase() +{ + qmlRegisterType<ExportedClass>("Test", 1, 0, "ExportedClass"); +} + +void tst_qdeclarativenotifier::createObjects() +{ + delete root; + root = 0; + exportedClass = exportedObject = 0; + + QDeclarativeComponent component(&engine, QUrl::fromLocalFile(SRCDIR "/data/connectnotify.qml")); + exportedObject = new ExportedClass(); + exportedObject->setObjectName("exportedObject"); + engine.rootContext()->setContextProperty("_exportedObject", exportedObject); + root = component.create(); + QVERIFY(root != 0); + exportedClass = qobject_cast<ExportedClass *>( + root->findChild<ExportedClass*>("exportedClass")); + QVERIFY(exportedClass != 0); +} + +void tst_qdeclarativenotifier::cleanupTestCase() +{ + delete root; + root = 0; + delete exportedObject; + exportedObject = 0; +} + +void tst_qdeclarativenotifier::connectNotify() +{ + createObjects(); + + QCOMPARE(exportedClass->selfPropConnections, 1); + QCOMPARE(exportedClass->qmlObjectPropConnections, 1); + QCOMPARE(exportedClass->cppObjectPropConnections, 0); + QCOMPARE(exportedClass->unboundPropConnections, 0); + QCOMPARE(exportedClass->normalBindingPropConnections, 1); + QCOMPARE(exportedClass->compiledBindingPropConnections, 1); + QCOMPARE(exportedClass->compiledBindingPropSharedConnections, 1); + QCOMPARE(exportedClass->boundSignalConnections, 1); + QCOMPARE(exportedClass->unusedSignalConnections, 0); + + QCOMPARE(exportedObject->selfPropConnections, 0); + QCOMPARE(exportedObject->qmlObjectPropConnections, 0); + QCOMPARE(exportedObject->cppObjectPropConnections, 1); + QCOMPARE(exportedObject->unboundPropConnections, 0); + QCOMPARE(exportedObject->normalBindingPropConnections, 0); + QCOMPARE(exportedObject->compiledBindingPropConnections, 0); + QCOMPARE(exportedObject->compiledBindingPropSharedConnections, 0); + QCOMPARE(exportedObject->boundSignalConnections, 0); + QCOMPARE(exportedObject->unusedSignalConnections, 0); +} + +void tst_qdeclarativenotifier::removeCompiledBinding() +{ + createObjects(); + + // Removing a binding should disconnect all of its guarded properties + QVERIFY(QMetaObject::invokeMethod(root, "removeCompiledBinding")); + QCOMPARE(exportedClass->compiledBindingPropConnections, 0); +} + +void tst_qdeclarativenotifier::removeCompiledBindingShared() +{ + createObjects(); + + // In this case, the compiledBindingPropShared property is used by two compiled bindings. + // Make sure that removing one binding doesn't by accident disconnect all. Behind the scenes, + // the subscription is shared between multiple bindings. + QVERIFY(QMetaObject::invokeMethod(root, "removeCompiledBindingShared_1")); + QCOMPARE(exportedClass->compiledBindingPropSharedConnections, 1); + + // Removing the second binding should trigger a disconnect now. + QVERIFY(QMetaObject::invokeMethod(root, "removeCompiledBindingShared_2")); + QCOMPARE(exportedClass->compiledBindingPropSharedConnections, 0); +} + +void tst_qdeclarativenotifier::removeNormalBinding() +{ + createObjects(); + + // Removing a binding should disconnect all of its guarded properties + QVERIFY(QMetaObject::invokeMethod(root, "removeNormalBinding")); + QCOMPARE(exportedClass->normalBindingPropConnections, 0); +} + +void tst_qdeclarativenotifier::readProperty() +{ + createObjects(); + + // Reading a property should not connect to it + QVERIFY(QMetaObject::invokeMethod(root, "readProperty")); + QCOMPARE(exportedClass->unboundPropConnections, 0); +} + +void tst_qdeclarativenotifier::propertyChange() +{ + createObjects(); + + // Changing the state will trigger the PropertyChange to overwrite a value with a binding. + // For this, the new binding needs to be connected, and afterwards disconnected. + QVERIFY(QMetaObject::invokeMethod(root, "changeState")); + QCOMPARE(exportedClass->unboundPropConnections, 1); + QVERIFY(QMetaObject::invokeMethod(root, "changeState")); + QCOMPARE(exportedClass->unboundPropConnections, 0); +} + +void tst_qdeclarativenotifier::disconnectOnDestroy() +{ + createObjects(); + + // Deleting a QML object should remove all connections. For exportedClass, this is tested in + // the destructor, and for exportedObject, it is tested below. + delete root; + root = 0; + QCOMPARE(exportedObject->cppObjectPropConnections, 0); +} + +QTEST_MAIN(tst_qdeclarativenotifier) + +#include "tst_qdeclarativenotifier.moc" + |