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

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