summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas McGuire <thomas.mcguire.qnx@kdab.com>2012-08-06 07:36:05 (GMT)
committerQt by Nokia <qt-info@nokia.com>2012-08-31 13:08:29 (GMT)
commit3c4c970962d75a902ff14a1c8b3adb36895c4ffa (patch)
tree6f6a4060c3e6b9179ac731398f7832427aa28fb1
parent43669c58027b9e6ba76b6b2ca6059ab3d9e8c0ea (diff)
downloadQt-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>
-rw-r--r--src/corelib/kernel/qobject_p.h15
-rw-r--r--src/declarative/qml/qdeclarativebinding.cpp17
-rw-r--r--src/declarative/qml/qdeclarativebinding_p.h33
-rw-r--r--src/declarative/qml/qdeclarativeboundsignal.cpp41
-rw-r--r--src/declarative/qml/qdeclarativeboundsignal_p.h28
-rw-r--r--src/declarative/qml/qdeclarativecompiledbindings.cpp72
-rw-r--r--src/declarative/qml/qdeclarativedata_p.h6
-rw-r--r--src/declarative/qml/qdeclarativeengine.cpp35
-rw-r--r--src/declarative/qml/qdeclarativenotifier.cpp5
-rw-r--r--src/declarative/qml/qdeclarativenotifier_p.h28
-rw-r--r--src/declarative/qml/qdeclarativeproperty.cpp6
-rw-r--r--src/declarative/qml/qdeclarativeproperty_p.h2
-rw-r--r--tests/auto/declarative/declarative.pro1
-rw-r--r--tests/auto/declarative/qdeclarativenotifier/data/Base.qml5
-rw-r--r--tests/auto/declarative/qdeclarativenotifier/data/connectnotify.qml69
-rw-r--r--tests/auto/declarative/qdeclarativenotifier/qdeclarativenotifier.pro12
-rw-r--r--tests/auto/declarative/qdeclarativenotifier/tst_qdeclarativenotifier.cpp302
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 = &notifier->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"
+