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/qqmlboundsignal_p.h>
7#include <private/qqmlcontext_p.h>
8#include <private/qqmlexpression_p.h>
9#include <private/qqmlproperty_p.h>
10#include <private/qqmlsignalnames_p.h>
11#include <private/qqmlvmemetaobject_p.h>
12#include <private/qv4jscall_p.h>
13#include <private/qv4qobjectwrapper_p.h>
14
15#include <QtQml/qqmlcontext.h>
16#include <QtQml/qqmlinfo.h>
17
18#include <QtCore/qdebug.h>
19#include <QtCore/qloggingcategory.h>
20#include <QtCore/qstringlist.h>
21
22#include <private/qobject_p.h>
23
24QT_BEGIN_NAMESPACE
25
26Q_LOGGING_CATEGORY(lcQmlConnections, "qt.qml.connections")
27
28// This is the equivalent of QQmlBoundSignal for C++ methods as as slots.
29// If a type derived from QQmlConnnections is compiled using qmltc, the
30// JavaScript functions it contains are turned into C++ methods and we cannot
31// use QQmlBoundSignal to connect to those.
32struct QQmlConnectionSlotDispatcher : public QtPrivate::QSlotObjectBase
33{
34 QV4::ExecutionEngine *v4 = nullptr;
35 QObject *receiver = nullptr;
36
37 // Signals rarely have more than one argument.
38 QQmlMetaObject::ArgTypeStorage<2> signalMetaTypes;
39 QQmlMetaObject::ArgTypeStorage<2> slotMetaTypes;
40
41 QMetaObject::Connection connection;
42
43 int slotIndex = -1;
44 bool enabled = true;
45
46 QQmlConnectionSlotDispatcher(
47 QV4::ExecutionEngine *v4, QObject *sender, int signalIndex,
48 QObject *receiver, int slotIndex, bool enabled)
49 : QtPrivate::QSlotObjectBase(&impl)
50 , v4(v4)
51 , receiver(receiver)
52 , slotIndex(slotIndex)
53 , enabled(enabled)
54 {
55 QMetaMethod signal = sender->metaObject()->method(index: signalIndex);
56 QQmlMetaObject::methodReturnAndParameterTypes(method: signal, argStorage: &signalMetaTypes, unknownTypeError: nullptr);
57
58 QMetaMethod slot = receiver->metaObject()->method(index: slotIndex);
59 QQmlMetaObject::methodReturnAndParameterTypes(method: slot, argStorage: &slotMetaTypes, unknownTypeError: nullptr);
60 }
61
62 template<typename ArgTypeStorage>
63 struct TypedFunction
64 {
65 Q_DISABLE_COPY_MOVE(TypedFunction)
66 public:
67 TypedFunction(const ArgTypeStorage *storage) : storage(storage) {}
68
69 QMetaType returnMetaType() const { return storage->at(0); }
70 qsizetype parameterCount() const { return storage->size() - 1; }
71 QMetaType parameterMetaType(qsizetype i) const { return storage->at(i + 1); }
72
73 private:
74 const ArgTypeStorage *storage;
75 };
76
77 static void impl(int which, QSlotObjectBase *base, QObject *, void **metaArgs, bool *ret)
78 {
79 switch (which) {
80 case Destroy: {
81 delete static_cast<QQmlConnectionSlotDispatcher *>(base);
82 break;
83 }
84 case Call: {
85 QQmlConnectionSlotDispatcher *self = static_cast<QQmlConnectionSlotDispatcher *>(base);
86 QV4::ExecutionEngine *v4 = self->v4;
87 if (!v4)
88 break;
89
90 if (!self->enabled)
91 break;
92
93 TypedFunction typedFunction(&self->slotMetaTypes);
94 QV4::coerceAndCall(
95 engine: v4, typedFunction: &typedFunction, argv: metaArgs,
96 types: self->signalMetaTypes.data(), argc: self->signalMetaTypes.size() - 1,
97 call: [&](void **argv, int) {
98 self->receiver->metaObject()->metacall(
99 self->receiver, QMetaObject::InvokeMetaMethod,
100 self->slotIndex, argv);
101 });
102
103 if (v4->hasException) {
104 QQmlError error = v4->catchExceptionAsQmlError();
105 if (QQmlEngine *qmlEngine = v4->qmlEngine()) {
106 QQmlEnginePrivate::get(e: qmlEngine)->warning(error);
107 } else {
108 QMessageLogger(
109 qPrintable(error.url().toString()), error.line(), nullptr)
110 .warning().noquote()
111 << error.toString();
112 }
113 }
114 break;
115 }
116 case Compare:
117 // We're not implementing the Compare protocol here. It's insane.
118 // QQmlConnectionSlotDispatcher compares false to anything. We use
119 // the regular QObject::disconnect with QMetaObject::Connection.
120 *ret = false;
121 break;
122 case NumOperations:
123 break;
124 }
125 };
126};
127
128class QQmlConnectionsPrivate : public QObjectPrivate
129{
130public:
131 QList<QBiPointer<QQmlBoundSignal, QQmlConnectionSlotDispatcher>> boundsignals;
132 QQmlGuard<QObject> target;
133
134 bool enabled = true;
135 bool targetSet = false;
136 bool ignoreUnknownSignals = false;
137 bool componentcomplete = true;
138
139 QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit;
140 QList<const QV4::CompiledData::Binding *> bindings;
141};
142
143/*!
144 \qmltype Connections
145 \nativetype QQmlConnections
146 \inqmlmodule QtQml
147 \ingroup qtquick-interceptors
148 \brief Describes generalized connections to signals.
149
150 A Connections object creates a connection to a QML signal.
151
152 When connecting to signals in QML, the usual way is to create an
153 "on<Signal>" handler that reacts when a signal is received, like this:
154
155 \qml
156 MouseArea {
157 onClicked: (mouse)=> { foo(mouse) }
158 }
159 \endqml
160
161 However, it is not possible to connect to a signal in this way in some
162 cases, such as when:
163
164 \list
165 \li Multiple connections to the same signal are required
166 \li Creating connections outside the scope of the signal sender
167 \li Connecting to targets not defined in QML
168 \endlist
169
170 When any of these are needed, the Connections type can be used instead.
171
172 For example, the above code can be changed to use a Connections object,
173 like this:
174
175 \qml
176 MouseArea {
177 Connections {
178 function onClicked(mouse) { foo(mouse) }
179 }
180 }
181 \endqml
182
183 More generally, the Connections object can be a child of some object other than
184 the sender of the signal:
185
186 \qml
187 MouseArea {
188 id: area
189 }
190 // ...
191 \endqml
192 \qml
193 Connections {
194 target: area
195 function onClicked(mouse) { foo(mouse) }
196 }
197 \endqml
198
199 \note For backwards compatibility you can also specify the signal handlers
200 without \c{function}, like you would specify them directly in the target
201 object. This is not recommended. If you specify one signal handler this way,
202 then all signal handlers specified as \c{function} in the same Connections
203 object are ignored.
204
205 \sa {Qt Qml}
206*/
207QQmlConnections::QQmlConnections(QObject *parent) :
208 QObject(*(new QQmlConnectionsPrivate), parent)
209{
210}
211
212QQmlConnections::~QQmlConnections()
213{
214 Q_D(QQmlConnections);
215
216 // The slot dispatchers hold cyclic references to their connections. Clear them.
217 for (const auto &bound : std::as_const(t&: d->boundsignals)) {
218 if (QQmlConnectionSlotDispatcher *dispatcher = bound.isT2() ? bound.asT2() : nullptr) {
219 // No need to explicitly disconnect anymore since 'this' is the receiver.
220 // But to be safe, explicitly break any cyclic references between the connection
221 // and the slot object.
222 dispatcher->connection = {};
223 dispatcher->destroyIfLastRef();
224 }
225 }
226}
227
228/*!
229 \qmlproperty QtObject QtQml::Connections::target
230 This property holds the object that sends the signal.
231
232 If this property is not set, the \c target defaults to the parent of the Connection.
233
234 If set to null, no connection is made and any signal handlers are ignored
235 until the target is not null.
236*/
237QObject *QQmlConnections::target() const
238{
239 Q_D(const QQmlConnections);
240 return d->targetSet ? d->target.data() : parent();
241}
242
243class QQmlBoundSignalDeleter : public QObject
244{
245public:
246 QQmlBoundSignalDeleter(QQmlBoundSignal *signal) : m_signal(signal) { m_signal->removeFromObject(); }
247 ~QQmlBoundSignalDeleter() { delete m_signal; }
248
249private:
250 QQmlBoundSignal *m_signal;
251};
252
253void QQmlConnections::setTarget(QObject *obj)
254{
255 Q_D(QQmlConnections);
256 if (d->targetSet && d->target == obj)
257 return;
258 d->targetSet = true; // even if setting to 0, it is *set*
259 for (const auto &bound : std::as_const(t&: d->boundsignals)) {
260 // It is possible that target is being changed due to one of our signal
261 // handlers -> use deleteLater().
262 if (QQmlBoundSignal *signal = bound.isT1() ? bound.asT1() : nullptr) {
263 if (signal->isNotifying())
264 (new QQmlBoundSignalDeleter(signal))->deleteLater();
265 else
266 delete signal;
267 } else {
268 QQmlConnectionSlotDispatcher *dispatcher = bound.asT2();
269 QObject::disconnect(std::exchange(obj&: dispatcher->connection, new_val: {}));
270 dispatcher->destroyIfLastRef();
271 }
272 }
273 d->boundsignals.clear();
274 d->target = obj;
275 connectSignals();
276 emit targetChanged();
277}
278
279/*!
280 \qmlproperty bool QtQml::Connections::enabled
281 \since 5.7
282
283 This property holds whether the item accepts change events.
284
285 By default, this property is \c true.
286*/
287bool QQmlConnections::isEnabled() const
288{
289 Q_D(const QQmlConnections);
290 return d->enabled;
291}
292
293void QQmlConnections::setEnabled(bool enabled)
294{
295 Q_D(QQmlConnections);
296 if (d->enabled == enabled)
297 return;
298
299 d->enabled = enabled;
300
301 for (const auto &bound : std::as_const(t&: d->boundsignals)) {
302 if (QQmlBoundSignal *signal = bound.isT1() ? bound.asT1() : nullptr)
303 signal->setEnabled(d->enabled);
304 else
305 bound.asT2()->enabled = enabled;
306 }
307
308 emit enabledChanged();
309}
310
311/*!
312 \qmlproperty bool QtQml::Connections::ignoreUnknownSignals
313
314 Normally, a connection to a non-existent signal produces runtime errors.
315
316 If this property is set to \c true, such errors are ignored.
317 This is useful if you intend to connect to different types of objects, handling
318 a different set of signals for each object.
319*/
320bool QQmlConnections::ignoreUnknownSignals() const
321{
322 Q_D(const QQmlConnections);
323 return d->ignoreUnknownSignals;
324}
325
326void QQmlConnections::setIgnoreUnknownSignals(bool ignore)
327{
328 Q_D(QQmlConnections);
329 d->ignoreUnknownSignals = ignore;
330}
331
332void QQmlConnectionsParser::verifyBindings(
333 const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
334 const QList<const QV4::CompiledData::Binding *> &props)
335{
336 for (int ii = 0; ii < props.size(); ++ii) {
337 const QV4::CompiledData::Binding *binding = props.at(i: ii);
338 const QString &propName = compilationUnit->stringAt(index: binding->propertyNameIndex);
339
340 if (!QQmlSignalNames::isHandlerName(signalName: propName)) {
341 error(binding: props.at(i: ii), description: QQmlConnections::tr(s: "Cannot assign to non-existent property \"%1\"").arg(a: propName));
342 return;
343 }
344
345 if (binding->type() == QV4::CompiledData::Binding::Type_Script)
346 continue;
347
348 if (binding->type() >= QV4::CompiledData::Binding::Type_Object) {
349 const QV4::CompiledData::Object *target = compilationUnit->objectAt(index: binding->value.objectIndex);
350 if (!compilationUnit->stringAt(index: target->inheritedTypeNameIndex).isEmpty())
351 error(binding, description: QQmlConnections::tr(s: "Connections: nested objects not allowed"));
352 else
353 error(binding, description: QQmlConnections::tr(s: "Connections: syntax error"));
354 return;
355 }
356
357 error(binding, description: QQmlConnections::tr(s: "Connections: script expected"));
358 return;
359 }
360}
361
362void QQmlConnectionsParser::applyBindings(QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
363{
364 QQmlConnectionsPrivate *p =
365 static_cast<QQmlConnectionsPrivate *>(QObjectPrivate::get(o: object));
366 p->compilationUnit = compilationUnit;
367 p->bindings = bindings;
368}
369
370void QQmlConnections::connectSignals()
371{
372 Q_D(QQmlConnections);
373 if (!d->componentcomplete || (d->targetSet && !target()))
374 return;
375
376 if (d->bindings.isEmpty()) {
377 connectSignalsToMethods();
378 } else {
379 if (lcQmlConnections().isWarningEnabled()) {
380 qmlWarning(me: this) << tr(s: "Implicitly defined onFoo properties in Connections are deprecated. "
381 "Use this syntax instead: function onFoo(<arguments>) { ... }");
382 }
383 connectSignalsToBindings();
384 }
385}
386
387void QQmlConnections::connectSignalsToMethods()
388{
389 Q_D(QQmlConnections);
390
391 QObject *target = this->target();
392 QQmlData *ddata = QQmlData::get(object: this);
393 if (!ddata)
394 return;
395
396 QV4::ExecutionEngine *engine = ddata->context->engine()->handle();
397
398 QQmlRefPointer<QQmlContextData> ctxtdata = ddata->outerContext;
399 for (int i = ddata->propertyCache->methodOffset(),
400 end = ddata->propertyCache->methodOffset() + ddata->propertyCache->methodCount();
401 i < end;
402 ++i) {
403
404 const QQmlPropertyData *handler = ddata->propertyCache->method(index: i);
405 if (!handler)
406 continue;
407
408 const QString propName = handler->name(object: this);
409
410 QQmlProperty prop(target, propName);
411 if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
412 QV4::Scope scope(engine);
413 QV4::ScopedContext global(scope, engine->rootContext());
414
415 if (QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(obj: this)) {
416 int signalIndex = QQmlPropertyPrivate::get(p: prop)->signalIndex();
417 auto *signal = new QQmlBoundSignal(target, signalIndex, this, qmlEngine(this));
418 signal->setEnabled(d->enabled);
419
420 QV4::Scoped<QV4::JavaScriptFunctionObject> method(
421 scope, vmeMetaObject->vmeMethod(index: handler->coreIndex()));
422
423 QQmlBoundSignalExpression *expression = ctxtdata
424 ? new QQmlBoundSignalExpression(
425 target, signalIndex, ctxtdata, this, method->function())
426 : nullptr;
427
428 signal->takeExpression(expression);
429 d->boundsignals += signal;
430 } else {
431 QQmlConnectionSlotDispatcher *slot = new QQmlConnectionSlotDispatcher(
432 scope.engine, target, prop.index(),
433 this, handler->coreIndex(), d->enabled);
434 slot->connection = QObjectPrivate::connect(
435 sender: target, signal_index: prop.index(), slotObj: slot, type: Qt::AutoConnection);
436 slot->ref();
437 d->boundsignals += slot;
438 }
439 } else if (!d->ignoreUnknownSignals
440 && propName.startsWith(s: QLatin1String("on")) && propName.size() > 2
441 && propName.at(i: 2).isUpper()) {
442 qmlWarning(me: this) << tr(s: "Detected function \"%1\" in Connections element. "
443 "This is probably intended to be a signal handler but no "
444 "signal of the target matches the name.").arg(a: propName);
445 }
446 }
447}
448
449// TODO: Drop this as soon as we can
450void QQmlConnections::connectSignalsToBindings()
451{
452 Q_D(QQmlConnections);
453 QObject *target = this->target();
454 QQmlData *ddata = QQmlData::get(object: this);
455 QQmlRefPointer<QQmlContextData> ctxtdata = ddata ? ddata->outerContext : nullptr;
456
457 for (const QV4::CompiledData::Binding *binding : std::as_const(t&: d->bindings)) {
458 Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Script);
459 QString propName = d->compilationUnit->stringAt(index: binding->propertyNameIndex);
460
461 QQmlProperty prop(target, propName);
462 if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
463 int signalIndex = QQmlPropertyPrivate::get(p: prop)->signalIndex();
464 QQmlBoundSignal *signal =
465 new QQmlBoundSignal(target, signalIndex, this, qmlEngine(this));
466 signal->setEnabled(d->enabled);
467
468 auto f = d->compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
469 QQmlBoundSignalExpression *expression =
470 ctxtdata ? new QQmlBoundSignalExpression(target, signalIndex, ctxtdata, this, f)
471 : nullptr;
472 signal->takeExpression(expression);
473 d->boundsignals += signal;
474 } else {
475 if (!d->ignoreUnknownSignals)
476 qmlWarning(me: this) << tr(s: "Cannot assign to non-existent property \"%1\"").arg(a: propName);
477 }
478 }
479}
480
481void QQmlConnections::classBegin()
482{
483 Q_D(QQmlConnections);
484 d->componentcomplete=false;
485}
486
487void QQmlConnections::componentComplete()
488{
489 Q_D(QQmlConnections);
490 d->componentcomplete=true;
491 connectSignals();
492}
493
494QT_END_NAMESPACE
495
496#include "moc_qqmlconnections_p.cpp"
497

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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