Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qqmlconnections.cpp
Go to the documentation of this file.
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
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.
40
42
43 int slotIndex = -1;
44 bool enabled = true;
45
48 QObject *receiver, int slotIndex, bool enabled)
49 : QtPrivate::QSlotObjectBase(&impl)
50 , v4(v4)
51 , receiver(receiver)
52 , slotIndex(slotIndex)
54 {
55 QMetaMethod signal = sender->metaObject()->method(signalIndex);
56 QQmlMetaObject::methodReturnAndParameterTypes(signal, &signalMetaTypes, nullptr);
57
58 QMetaMethod slot = receiver->metaObject()->method(slotIndex);
59 QQmlMetaObject::methodReturnAndParameterTypes(slot, &slotMetaTypes, nullptr);
60 }
61
62 template<typename ArgTypeStorage>
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: {
86 QV4::ExecutionEngine *v4 = self->v4;
87 if (!v4)
88 break;
89
90 if (!self->enabled)
91 break;
92
93 TypedFunction typedFunction(&self->slotMetaTypes);
95 v4, &typedFunction, metaArgs,
96 self->signalMetaTypes.data(), self->signalMetaTypes.size() - 1,
97 [&](void **argv, int) {
98 self->receiver->metaObject()->metacall(
99 self->receiver, QMetaObject::InvokeMetaMethod,
100 self->slotIndex, argv);
101 });
102
103 if (v4->hasException) {
105 if (QQmlEngine *qmlEngine = v4->qmlEngine()) {
107 } else {
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
129{
130public:
131 QList<QBiPointer<QQmlBoundSignal, QQmlConnectionSlotDispatcher>> boundsignals;
132 QQmlGuard<QObject> target;
133
134 bool enabled = true;
135 bool targetSet = false;
137 bool componentcomplete = true;
138
139 QQmlRefPointer<QV4::ExecutableCompilationUnit> compilationUnit;
140 QList<const QV4::CompiledData::Binding *> bindings;
141};
142
211
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(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
238{
239 Q_D(const QQmlConnections);
240 return d->targetSet ? d->target.data() : parent();
241}
242
244{
245public:
247 ~QQmlBoundSignalDeleter() { delete m_signal; }
248
249private:
250 QQmlBoundSignal *m_signal;
251};
252
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(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())
265 else
266 delete signal;
267 } else {
268 QQmlConnectionSlotDispatcher *dispatcher = bound.asT2();
269 QObject::disconnect(std::exchange(dispatcher->connection, {}));
270 dispatcher->destroyIfLastRef();
271 }
272 }
273 d->boundsignals.clear();
274 d->target = obj;
275 connectSignals();
277}
278
288{
289 Q_D(const QQmlConnections);
290 return d->enabled;
291}
292
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(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
321{
322 Q_D(const QQmlConnections);
323 return d->ignoreUnknownSignals;
324}
325
327{
328 Q_D(QQmlConnections);
329 d->ignoreUnknownSignals = ignore;
330}
331
332void QQmlConnectionsParser::verifyBindings(const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &props)
333{
334 for (int ii = 0; ii < props.size(); ++ii) {
335 const QV4::CompiledData::Binding *binding = props.at(ii);
336 const QString &propName = compilationUnit->stringAt(binding->propertyNameIndex);
337
338 if (!QQmlSignalNames::isHandlerName(propName)) {
339 error(props.at(ii), QQmlConnections::tr("Cannot assign to non-existent property \"%1\"").arg(propName));
340 return;
341 }
342
344 continue;
345
347 const QV4::CompiledData::Object *target = compilationUnit->objectAt(binding->value.objectIndex);
348 if (!compilationUnit->stringAt(target->inheritedTypeNameIndex).isEmpty())
349 error(binding, QQmlConnections::tr("Connections: nested objects not allowed"));
350 else
351 error(binding, QQmlConnections::tr("Connections: syntax error"));
352 return;
353 }
354
355 error(binding, QQmlConnections::tr("Connections: script expected"));
356 return;
357 }
358}
359
360void QQmlConnectionsParser::applyBindings(QObject *object, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit, const QList<const QV4::CompiledData::Binding *> &bindings)
361{
363 static_cast<QQmlConnectionsPrivate *>(QObjectPrivate::get(object));
364 p->compilationUnit = compilationUnit;
365 p->bindings = bindings;
366}
367
368void QQmlConnections::connectSignals()
369{
370 Q_D(QQmlConnections);
371 if (!d->componentcomplete || (d->targetSet && !target()))
372 return;
373
374 if (d->bindings.isEmpty()) {
375 connectSignalsToMethods();
376 } else {
377 if (lcQmlConnections().isWarningEnabled()) {
378 qmlWarning(this) << tr("Implicitly defined onFoo properties in Connections are deprecated. "
379 "Use this syntax instead: function onFoo(<arguments>) { ... }");
380 }
381 connectSignalsToBindings();
382 }
383}
384
385void QQmlConnections::connectSignalsToMethods()
386{
387 Q_D(QQmlConnections);
388
389 QObject *target = this->target();
390 QQmlData *ddata = QQmlData::get(this);
391 if (!ddata)
392 return;
393
394 QV4::ExecutionEngine *engine = ddata->context->engine()->handle();
395
396 QQmlRefPointer<QQmlContextData> ctxtdata = ddata->outerContext;
397 for (int i = ddata->propertyCache->methodOffset(),
398 end = ddata->propertyCache->methodOffset() + ddata->propertyCache->methodCount();
399 i < end;
400 ++i) {
401
402 const QQmlPropertyData *handler = ddata->propertyCache->method(i);
403 if (!handler)
404 continue;
405
406 const QString propName = handler->name(this);
407
408 QQmlProperty prop(target, propName);
409 if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
410 QV4::Scope scope(engine);
411 QV4::ScopedContext global(scope, engine->rootContext());
412
413 if (QQmlVMEMetaObject *vmeMetaObject = QQmlVMEMetaObject::get(this)) {
414 int signalIndex = QQmlPropertyPrivate::get(prop)->signalIndex();
415 auto *signal = new QQmlBoundSignal(target, signalIndex, this, qmlEngine(this));
416 signal->setEnabled(d->enabled);
417
419 scope, vmeMetaObject->vmeMethod(handler->coreIndex()));
420
421 QQmlBoundSignalExpression *expression = ctxtdata
423 target, signalIndex, ctxtdata, this, method->function())
424 : nullptr;
425
426 signal->takeExpression(expression);
427 d->boundsignals += signal;
428 } else {
430 scope.engine, target, prop.index(),
431 this, handler->coreIndex(), d->enabled);
433 target, prop.index(), slot, Qt::AutoConnection);
434 slot->ref();
435 d->boundsignals += slot;
436 }
437 } else if (!d->ignoreUnknownSignals
438 && propName.startsWith(QLatin1String("on")) && propName.size() > 2
439 && propName.at(2).isUpper()) {
440 qmlWarning(this) << tr("Detected function \"%1\" in Connections element. "
441 "This is probably intended to be a signal handler but no "
442 "signal of the target matches the name.").arg(propName);
443 }
444 }
445}
446
447// TODO: Drop this as soon as we can
448void QQmlConnections::connectSignalsToBindings()
449{
450 Q_D(QQmlConnections);
451 QObject *target = this->target();
452 QQmlData *ddata = QQmlData::get(this);
453 QQmlRefPointer<QQmlContextData> ctxtdata = ddata ? ddata->outerContext : nullptr;
454
455 for (const QV4::CompiledData::Binding *binding : std::as_const(d->bindings)) {
457 QString propName = d->compilationUnit->stringAt(binding->propertyNameIndex);
458
459 QQmlProperty prop(target, propName);
460 if (prop.isValid() && (prop.type() & QQmlProperty::SignalProperty)) {
461 int signalIndex = QQmlPropertyPrivate::get(prop)->signalIndex();
463 new QQmlBoundSignal(target, signalIndex, this, qmlEngine(this));
464 signal->setEnabled(d->enabled);
465
466 auto f = d->compilationUnit->runtimeFunctions[binding->value.compiledScriptIndex];
467 QQmlBoundSignalExpression *expression =
468 ctxtdata ? new QQmlBoundSignalExpression(target, signalIndex, ctxtdata, this, f)
469 : nullptr;
470 signal->takeExpression(expression);
471 d->boundsignals += signal;
472 } else {
473 if (!d->ignoreUnknownSignals)
474 qmlWarning(this) << tr("Cannot assign to non-existent property \"%1\"").arg(propName);
475 }
476 }
477}
478
480{
481 Q_D(QQmlConnections);
482 d->componentcomplete=false;
483}
484
486{
487 Q_D(QQmlConnections);
488 d->componentcomplete=true;
489 connectSignals();
490}
491
493
494#include "moc_qqmlconnections_p.cpp"
QV4::ExecutionEngine * handle() const
Definition qjsengine.h:298
\inmodule QtCore
Definition qlogging.h:72
void void Q_DECL_COLD_FUNCTION void warning(const char *msg,...) const Q_ATTRIBUTE_FORMAT_PRINTF(2
Logs a warning message specified with format msg.
Definition qlogging.cpp:625
\inmodule QtCore
Definition qmetaobject.h:19
\inmodule QtCore Represents a handle to a signal-slot (or signal-functor) connection.
\inmodule QtCore
Definition qmetatype.h:341
static QObjectPrivate * get(QObject *o)
Definition qobject_p.h:150
static QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer< Func1 >::Object *sender, Func1 signal, const typename QtPrivate::FunctionPointer< Func2 >::Object *receiverPrivate, Func2 slot, Qt::ConnectionType type=Qt::AutoConnection)
Definition qobject_p.h:299
\inmodule QtCore
Definition qobject.h:103
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
QQmlBoundSignalDeleter(QQmlBoundSignal *signal)
void applyBindings(QObject *object, const QQmlRefPointer< QV4::ExecutableCompilationUnit > &compilationUnit, const QList< const QV4::CompiledData::Binding * > &bindings) override
void verifyBindings(const QQmlRefPointer< QV4::ExecutableCompilationUnit > &compilationUnit, const QList< const QV4::CompiledData::Binding * > &props) override
QList< QBiPointer< QQmlBoundSignal, QQmlConnectionSlotDispatcher > > boundsignals
QQmlRefPointer< QV4::ExecutableCompilationUnit > compilationUnit
QList< const QV4::CompiledData::Binding * > bindings
QQmlGuard< QObject > target
bool isEnabled() const
\qmlproperty bool QtQml::Connections::enabled
void setEnabled(bool enabled)
void setTarget(QObject *)
void componentComplete() override
Invoked after the root component that caused this instantiation has completed construction.
void setIgnoreUnknownSignals(bool ignore)
void targetChanged()
void classBegin() override
Invoked after class creation, but before any properties have been set.
QQmlConnections(QObject *parent=nullptr)
static QQmlData * get(QObjectPrivate *priv, bool create)
Definition qqmldata_p.h:199
static QQmlEnginePrivate * get(QQmlEngine *e)
The QQmlEngine class provides an environment for instantiating QML components.
Definition qqmlengine.h:57
The QQmlError class encapsulates a QML error.
Definition qqmlerror.h:18
static bool methodReturnAndParameterTypes(const QMetaMethod &method, ArgTypeStorage *argStorage, QByteArray *unknownTypeError)
QVarLengthArray< QMetaType, Prealloc > ArgTypeStorage
QString name(QObject *) const
static QQmlPropertyPrivate * get(const QQmlProperty &p)
The QQmlProperty class abstracts accessing properties on objects created from QML.
static bool isHandlerName(QStringView signalName)
static QQmlVMEMetaObject * get(QObject *o)
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5455
qsizetype size() const noexcept
Returns the number of characters in this string.
Definition qstring.h:186
const QChar at(qsizetype i) const
Returns the character at the given index position in the string.
Definition qstring.h:1226
QChar * data()
Returns a pointer to the data stored in the QString.
Definition qstring.h:1240
auto signalIndex
auto signal
Combined button and popup list for selecting options.
ReturnedValue coerceAndCall(ExecutionEngine *engine, const Function::JSTypedFunction *typedFunction, const CompiledData::Function *compiledFunction, const Value *argv, int argc, Callable call)
Scoped< ExecutionContext > ScopedContext
\macro QT_NO_KEYWORDS >
@ AutoConnection
DBusConnection const char DBusError * error
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char * method
#define Q_LOGGING_CATEGORY(name,...)
return ret
GLuint GLuint end
GLenum GLenum GLsizei const GLuint GLboolean enabled
GLfloat GLfloat f
GLenum target
GLenum GLuint GLsizei const GLenum * props
GLhandleARB obj
[2]
GLfloat GLfloat p
[1]
QQmlEngine * qmlEngine(const QObject *obj)
Definition qqml.cpp:80
Q_QML_EXPORT QQmlInfo qmlWarning(const QObject *me)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define qPrintable(string)
Definition qstring.h:1531
#define tr(X)
#define emit
ptrdiff_t qsizetype
Definition qtypes.h:165
static const uint base
Definition qurlidna.cpp:20
QStorageInfo storage
[1]
QObject::connect nullptr
QJSValue global
QJSEngine engine
[0]
TypedFunction(const ArgTypeStorage *storage)
QMetaType parameterMetaType(qsizetype i) const
QQmlMetaObject::ArgTypeStorage< 2 > slotMetaTypes
QQmlConnectionSlotDispatcher(QV4::ExecutionEngine *v4, QObject *sender, int signalIndex, QObject *receiver, int slotIndex, bool enabled)
static void impl(int which, QSlotObjectBase *base, QObject *, void **metaArgs, bool *ret)
QQmlMetaObject::ArgTypeStorage< 2 > signalMetaTypes
QMetaObject::Connection connection
union QV4::CompiledData::Binding::@540 value
QQmlError catchExceptionAsQmlError()
QQmlEngine * qmlEngine() const