1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmlconnections_p.h"
5
6#include <private/qqmlexpression_p.h>
7#include <private/qqmlproperty_p.h>
8#include <private/qqmlboundsignal_p.h>
9#include <qqmlcontext.h>
10#include <private/qqmlcontext_p.h>
11#include <private/qqmlvmemetaobject_p.h>
12#include <qqmlinfo.h>
13
14#include <QtCore/qloggingcategory.h>
15#include <QtCore/qdebug.h>
16#include <QtCore/qstringlist.h>
17
18#include <private/qobject_p.h>
19
20QT_BEGIN_NAMESPACE
21
22Q_LOGGING_CATEGORY(lcQmlConnections, "qt.qml.connections")
23
24class QQmlConnectionsPrivate : public QObjectPrivate
25{
26public:
27 QList<QQmlBoundSignal*> boundsignals;
28 QQmlGuard<QObject> target;
29
30 bool enabled = true;
31 bool targetSet = false;
32 bool ignoreUnknownSignals = false;
33 bool componentcomplete = true;
34
35 QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit;
36 QList<const QV4::CompiledData::Binding *> bindings;
37};
38
39/*!
40 \qmltype Connections
41 \instantiates QQmlConnections
42 \inqmlmodule QtQml
43 \ingroup qtquick-interceptors
44 \brief Describes generalized connections to signals.
45
46 A Connections object creates a connection to a QML signal.
47
48 When connecting to signals in QML, the usual way is to create an
49 "on<Signal>" handler that reacts when a signal is received, like this:
50
51 \qml
52 MouseArea {
53 onClicked: (mouse)=> { foo(mouse) }
54 }
55 \endqml
56
57 However, it is not possible to connect to a signal in this way in some
58 cases, such as when:
59
60 \list
61 \li Multiple connections to the same signal are required
62 \li Creating connections outside the scope of the signal sender
63 \li Connecting to targets not defined in QML
64 \endlist
65
66 When any of these are needed, the Connections type can be used instead.
67
68 For example, the above code can be changed to use a Connections object,
69 like this:
70
71 \qml
72 MouseArea {
73 Connections {
74 function onClicked(mouse) { foo(mouse) }
75 }
76 }
77 \endqml
78
79 More generally, the Connections object can be a child of some object other than
80 the sender of the signal:
81
82 \qml
83 MouseArea {
84 id: area
85 }
86 // ...
87 \endqml
88 \qml
89 Connections {
90 target: area
91 function onClicked(mouse) { foo(mouse) }
92 }
93 \endqml
94
95 \note For backwards compatibility you can also specify the signal handlers
96 without \c{function}, like you would specify them directly in the target
97 object. This is not recommended. If you specify one signal handler this way,
98 then all signal handlers specified as \c{function} in the same Connections
99 object are ignored.
100
101 \sa {Qt QML}
102*/
103QQmlConnections::QQmlConnections(QObject *parent) :
104 QObject(*(new QQmlConnectionsPrivate), parent)
105{
106}
107
108/*!
109 \qmlproperty QtObject QtQml::Connections::target
110 This property holds the object that sends the signal.
111
112 If this property is not set, the \c target defaults to the parent of the Connection.
113
114 If set to null, no connection is made and any signal handlers are ignored
115 until the target is not null.
116*/
117QObject *QQmlConnections::target() const
118{
119 Q_D(const QQmlConnections);
120 return d->targetSet ? d->target.data() : parent();
121}
122
123class QQmlBoundSignalDeleter : public QObject
124{
125public:
126 QQmlBoundSignalDeleter(QQmlBoundSignal *signal) : m_signal(signal) { m_signal->removeFromObject(); }
127 ~QQmlBoundSignalDeleter() { delete m_signal; }
128
129private:
130 QQmlBoundSignal *m_signal;
131};
132
133void QQmlConnections::setTarget(QObject *obj)
134{
135 Q_D(QQmlConnections);
136 if (d->targetSet && d->target == obj)
137 return;
138 d->targetSet = true; // even if setting to 0, it is *set*
139 for (QQmlBoundSignal *s : std::as_const(t&: d->boundsignals)) {
140 // It is possible that target is being changed due to one of our signal
141 // handlers -> use deleteLater().
142 if (s->isNotifying())
143 (new QQmlBoundSignalDeleter(s))->deleteLater();
144 else
145 delete s;
146 }
147 d->boundsignals.clear();
148 d->target = obj;
149 connectSignals();
150 emit targetChanged();
151}
152
153/*!
154 \qmlproperty bool QtQml::Connections::enabled
155 \since 5.7
156
157 This property holds whether the item accepts change events.
158
159 By default, this property is \c true.
160*/
161bool QQmlConnections::isEnabled() const
162{
163 Q_D(const QQmlConnections);
164 return d->enabled;
165}
166
167void QQmlConnections::setEnabled(bool enabled)
168{
169 Q_D(QQmlConnections);
170 if (d->enabled == enabled)
171 return;
172
173 d->enabled = enabled;
174
175 for (QQmlBoundSignal *s : std::as_const(t&: d->boundsignals))
176 s->setEnabled(d->enabled);
177
178 emit enabledChanged();
179}
180
181/*!
182 \qmlproperty bool QtQml::Connections::ignoreUnknownSignals
183
184 Normally, a connection to a non-existent signal produces runtime errors.
185
186 If this property is set to \c true, such errors are ignored.
187 This is useful if you intend to connect to different types of objects, handling
188 a different set of signals for each object.
189*/
190bool QQmlConnections::ignoreUnknownSignals() const
191{
192 Q_D(const QQmlConnections);
193 return d->ignoreUnknownSignals;
194}
195
196void QQmlConnections::setIgnoreUnknownSignals(bool ignore)
197{
198 Q_D(QQmlConnections);
199 d->ignoreUnknownSignals = ignore;
200}
201
202void QQmlConnectionsParser::verifyBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &props)
203{
204 for (int ii = 0; ii < props.size(); ++ii) {
205 const QV4::CompiledData::Binding *binding = props.at(i: ii);
206 const QString &propName = compilationUnit->stringAt(index: binding->propertyNameIndex);
207
208 const bool thirdCharacterIsValid = (propName.size() >= 2)
209 && (propName.at(i: 2).isUpper() || propName.at(i: 2) == u'_');
210 if (!propName.startsWith(s: QLatin1String("on")) || !thirdCharacterIsValid) {
211 error(binding: props.at(i: ii), description: QQmlConnections::tr(s: "Cannot assign to non-existent property \"%1\"").arg(a: propName));
212 return;
213 }
214
215 if (binding->type() == QV4::CompiledData::Binding::Type_Script)
216 continue;
217
218 if (binding->type() >= QV4::CompiledData::Binding::Type_Object) {
219 const QV4::CompiledData::Object *target = compilationUnit->objectAt(index: binding->value.objectIndex);
220 if (!compilationUnit->stringAt(index: target->inheritedTypeNameIndex).isEmpty())
221 error(binding, description: QQmlConnections::tr(s: "Connections: nested objects not allowed"));
222 else
223 error(binding, description: QQmlConnections::tr(s: "Connections: syntax error"));
224 return;
225 }
226
227 error(binding, description: QQmlConnections::tr(s: "Connections: script expected"));
228 return;
229 }
230}
231
232void QQmlConnectionsParser::applyBindings(QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
233{
234 QQmlConnectionsPrivate *p =
235 static_cast<QQmlConnectionsPrivate *>(QObjectPrivate::get(o: object));
236 p->compilationUnit = compilationUnit;
237 p->bindings = bindings;
238}
239
240void QQmlConnections::connectSignals()
241{
242 Q_D(QQmlConnections);
243 if (!d->componentcomplete || (d->targetSet && !target()))
244 return;
245
246 if (d->bindings.isEmpty()) {
247 connectSignalsToMethods();
248 } else {
249 if (lcQmlConnections().isWarningEnabled()) {
250 qmlWarning(me: this) << tr(s: "Implicitly defined onFoo properties in Connections are deprecated. "
251 "Use this syntax instead: function onFoo(<arguments>) { ... }");
252 }
253 connectSignalsToBindings();
254 }
255}
256
257void QQmlConnections::connectSignalsToMethods()
258{
259 Q_D(QQmlConnections);
260
261 QObject *target = this->target();
262 QQmlData *ddata = QQmlData::get(object: this);
263 if (!ddata)
264 return;
265
266 QV4::ExecutionEngine *engine = ddata->context->engine()->handle();
267
268 QQmlRefPointer<QQmlContextData> ctxtdata = ddata->outerContext;
269 for (int i = ddata->propertyCache->methodOffset(),
270 end = ddata->propertyCache->methodOffset() + ddata->propertyCache->methodCount();
271 i < end;
272 ++i) {
273
274 const QQmlPropertyData *handler = ddata->propertyCache->method(index: i);
275 if (!handler || !handler->isVMEFunction())
276 continue;
277
278 const QString propName = handler->name(this);
279
280 QQmlProperty prop(target, propName);
281 if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
282 int signalIndex = QQmlPropertyPrivate::get(p: prop)->signalIndex();
283 auto *signal = new QQmlBoundSignal(target, signalIndex, this, qmlEngine(this));
284 signal->setEnabled(d->enabled);
285
286 QV4::Scope scope(engine);
287 QV4::ScopedContext global(scope, engine->rootContext());
288
289 QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(obj: this);
290 Q_ASSERT(vmeMetaObject); // the fact we found the property above should guarentee this
291
292 QV4::ScopedFunctionObject method(scope, vmeMetaObject->vmeMethod(index: handler->coreIndex()));
293
294 QQmlBoundSignalExpression *expression =
295 ctxtdata ? new QQmlBoundSignalExpression(
296 target, signalIndex, ctxtdata, this,
297 method->as<QV4::FunctionObject>()->function())
298 : nullptr;
299
300 signal->takeExpression(expression);
301 d->boundsignals += signal;
302 } else if (!d->ignoreUnknownSignals
303 && propName.startsWith(s: QLatin1String("on")) && propName.size() > 2
304 && propName.at(i: 2).isUpper()) {
305 qmlWarning(me: this) << tr(s: "Detected function \"%1\" in Connections element. "
306 "This is probably intended to be a signal handler but no "
307 "signal of the target matches the name.").arg(a: propName);
308 }
309 }
310}
311
312// TODO: Drop this as soon as we can
313void QQmlConnections::connectSignalsToBindings()
314{
315 Q_D(QQmlConnections);
316 QObject *target = this->target();
317 QQmlData *ddata = QQmlData::get(object: this);
318 QQmlRefPointer<QQmlContextData> ctxtdata = ddata ? ddata->outerContext : nullptr;
319
320 for (const QV4::CompiledData::Binding *binding : std::as_const(t&: d->bindings)) {
321 Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Script);
322 QString propName = d->compilationUnit->stringAt(index: binding->propertyNameIndex);
323
324 QQmlProperty prop(target, propName);
325 if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
326 int signalIndex = QQmlPropertyPrivate::get(p: prop)->signalIndex();
327 QQmlBoundSignal *signal =
328 new QQmlBoundSignal(target, signalIndex, this, qmlEngine(this));
329 signal->setEnabled(d->enabled);
330
331 auto f = d->compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
332 QQmlBoundSignalExpression *expression =
333 ctxtdata ? new QQmlBoundSignalExpression(target, signalIndex, ctxtdata, this, f)
334 : nullptr;
335 signal->takeExpression(expression);
336 d->boundsignals += signal;
337 } else {
338 if (!d->ignoreUnknownSignals)
339 qmlWarning(me: this) << tr(s: "Cannot assign to non-existent property \"%1\"").arg(a: propName);
340 }
341 }
342}
343
344void QQmlConnections::classBegin()
345{
346 Q_D(QQmlConnections);
347 d->componentcomplete=false;
348}
349
350void QQmlConnections::componentComplete()
351{
352 Q_D(QQmlConnections);
353 d->componentcomplete=true;
354 connectSignals();
355}
356
357QT_END_NAMESPACE
358
359#include "moc_qqmlconnections_p.cpp"
360

source code of qtdeclarative/src/qml/types/qqmlconnections.cpp