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 | |
56 | QT_BEGIN_NAMESPACE |
57 | |
58 | Q_LOGGING_CATEGORY(lcQmlConnections, "qt.qml.connections" ) |
59 | |
60 | class QQmlConnectionsPrivate : public QObjectPrivate |
61 | { |
62 | public: |
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 | */ |
135 | QQmlConnections::QQmlConnections(QObject *parent) : |
136 | QObject(*(new QQmlConnectionsPrivate), parent) |
137 | { |
138 | } |
139 | |
140 | QQmlConnections::~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 | */ |
153 | QObject *QQmlConnections::target() const |
154 | { |
155 | Q_D(const QQmlConnections); |
156 | return d->targetSet ? d->target : parent(); |
157 | } |
158 | |
159 | class QQmlBoundSignalDeleter : public QObject |
160 | { |
161 | public: |
162 | QQmlBoundSignalDeleter(QQmlBoundSignal *signal) : m_signal(signal) { m_signal->removeFromObject(); } |
163 | ~QQmlBoundSignalDeleter() { delete m_signal; } |
164 | |
165 | private: |
166 | QQmlBoundSignal *m_signal; |
167 | }; |
168 | |
169 | void 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 | */ |
197 | bool QQmlConnections::isEnabled() const |
198 | { |
199 | Q_D(const QQmlConnections); |
200 | return d->enabled; |
201 | } |
202 | |
203 | void 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 | */ |
226 | bool QQmlConnections::ignoreUnknownSignals() const |
227 | { |
228 | Q_D(const QQmlConnections); |
229 | return d->ignoreUnknownSignals; |
230 | } |
231 | |
232 | void QQmlConnections::setIgnoreUnknownSignals(bool ignore) |
233 | { |
234 | Q_D(QQmlConnections); |
235 | d->ignoreUnknownSignals = ignore; |
236 | } |
237 | |
238 | void 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 | |
264 | void 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 | |
272 | void 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 | |
291 | void 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 |
347 | void 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 | |
378 | void QQmlConnections::classBegin() |
379 | { |
380 | Q_D(QQmlConnections); |
381 | d->componentcomplete=false; |
382 | } |
383 | |
384 | void QQmlConnections::componentComplete() |
385 | { |
386 | Q_D(QQmlConnections); |
387 | d->componentcomplete=true; |
388 | connectSignals(); |
389 | } |
390 | |
391 | QT_END_NAMESPACE |
392 | |
393 | #include "moc_qqmlconnections_p.cpp" |
394 | |