Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlboundsignal.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
5
6#include <private/qmetaobject_p.h>
7#include <private/qmetaobjectbuilder_p.h>
8#include "qqmlengine_p.h"
9#include "qqmlglobal_p.h"
10#include <private/qqmlprofiler_p.h>
11#include <private/qqmldebugconnector_p.h>
12#include <private/qqmldebugserviceinterfaces_p.h>
13#include "qqmlinfo.h"
14
15#include <private/qjsvalue_p.h>
16#include <private/qv4value_p.h>
17#include <private/qv4jscall_p.h>
18#include <private/qv4qobjectwrapper_p.h>
19#include <private/qv4qmlcontext_p.h>
20
21#include <QtCore/qdebug.h>
22
23#include <qtqml_tracepoints_p.h>
24
26
27Q_TRACE_POINT(qtqml, QQmlHandlingSignal_entry, const QQmlEngine *engine, const QString &function,
28 const QString &fileName, int line, int column)
29Q_TRACE_POINT(qtqml, QQmlHandlingSignal_exit)
30
31/*!
32 \class QQmlBoundSignal
33 \inmodule QtQml
34 \internal
35*/
36
37QQmlBoundSignalExpression::QQmlBoundSignalExpression(const QObject *target, int index, const QQmlRefPointer<QQmlContextData> &ctxt, QObject *scope,
38 const QString &expression, const QString &fileName, quint16 line, quint16 column,
39 const QString &handlerName, const QString &parameterString)
40 : QQmlJavaScriptExpression(),
41 m_index(index),
42 m_target(target)
43{
44 init(ctxt, scope);
45
46 QV4::ExecutionEngine *v4 = engine()->handle();
47
48 QString function;
49
50 // Add some leading whitespace to account for the binding's column offset.
51 // It's 2 off because a, we start counting at 1 and b, the '(' below is not counted.
52 function += QString(qMax(column, (quint16)2) - 2, QChar(QChar::Space))
53 + QLatin1String("(function ") + handlerName + QLatin1Char('(');
54
55 if (parameterString.isEmpty()) {
56 QString error;
57 //TODO: look at using the property cache here (as in the compiler)
58 // for further optimization
59 QMetaMethod signal = QMetaObjectPrivate::signal(m_target->metaObject(), m_index);
60 function += QQmlPropertyCache::signalParameterStringForJS(signal.parameterNames(), &error);
61
62 if (!error.isEmpty()) {
63 qmlWarning(scopeObject()) << error;
64 return;
65 }
66 } else
67 function += parameterString;
68
69 function += QLatin1String(") { ") + expression + QLatin1String(" })");
70 QV4::Scope valueScope(v4);
71 QV4::Scoped<QV4::JavaScriptFunctionObject> f(
72 valueScope, evalFunction(context(), scopeObject(), function, fileName, line));
73 QV4::ScopedContext context(valueScope, f->scope());
74 setupFunction(context, f->function());
75}
76
77QQmlBoundSignalExpression::QQmlBoundSignalExpression(const QObject *target, int index, const QQmlRefPointer<QQmlContextData> &ctxt,
78 QObject *scopeObject, QV4::Function *function, QV4::ExecutionContext *scope)
79 : QQmlJavaScriptExpression(),
80 m_index(index),
81 m_target(target)
82{
83 // It's important to call init first, because m_index gets remapped in case of cloned signals.
84 init(ctxt, scopeObject);
85
86 QV4::ExecutionEngine *engine = ctxt->engine()->handle();
87
88 if (!function->isClosureWrapper()) {
89 QList<QByteArray> signalParameters = QMetaObjectPrivate::signal(m_target->metaObject(), m_index).parameterNames();
90 if (!signalParameters.isEmpty()) {
91 QString error;
92 QQmlPropertyCache::signalParameterStringForJS(signalParameters, &error);
93 if (!error.isEmpty()) {
94 qmlWarning(scopeObject) << error;
95 return;
96 }
97 function->updateInternalClass(engine, signalParameters);
98 }
99 }
100
101 QV4::Scope valueScope(engine);
102 QV4::Scoped<QV4::QmlContext> qmlContext(valueScope, scope);
103 if (!qmlContext)
104 qmlContext = QV4::QmlContext::create(engine->rootContext(), ctxt, scopeObject);
105 if (auto closure = function->nestedFunction()) {
106 // If the function is marked as having a nested function, then the user wrote:
107 // onSomeSignal: function() { /*....*/ }
108 // So take that nested function:
109 setupFunction(qmlContext, closure);
110 } else {
111 setupFunction(qmlContext, function);
112
113 // If it's a closure wrapper but we cannot directly access the nested function
114 // we need to run the outer function to get the nested one.
115 if (function->isClosureWrapper()) {
116 bool isUndefined = false;
117 QV4::Scoped<QV4::JavaScriptFunctionObject> result(
118 valueScope, QQmlJavaScriptExpression::evaluate(&isUndefined));
119
120 Q_ASSERT(!isUndefined);
121 Q_ASSERT(result->function());
122 Q_ASSERT(result->function()->compilationUnit == function->compilationUnit);
123
124 QV4::Scoped<QV4::ExecutionContext> callContext(valueScope, result->scope());
125 setupFunction(callContext, result->function());
126 }
127 }
128}
129
130void QQmlBoundSignalExpression::init(const QQmlRefPointer<QQmlContextData> &ctxt, QObject *scope)
131{
132 setNotifyOnValueChanged(false);
133 setContext(ctxt);
134 setScopeObject(scope);
135
136 Q_ASSERT(m_target && m_index > -1);
137 m_index = QQmlPropertyCache::originalClone(m_target, m_index);
138}
139
140QQmlBoundSignalExpression::~QQmlBoundSignalExpression()
141{
142}
143
144QString QQmlBoundSignalExpression::expressionIdentifier() const
145{
146 QQmlSourceLocation loc = sourceLocation();
147 return loc.sourceFile + QLatin1Char(':') + QString::number(loc.line);
148}
149
150void QQmlBoundSignalExpression::expressionChanged()
151{
152 // bound signals do not notify on change.
153}
154
155QString QQmlBoundSignalExpression::expression() const
156{
157 if (expressionFunctionValid())
158 return QStringLiteral("function() { [native code] }");
159 return QString();
160}
161
162// Parts of this function mirror code in QQmlExpressionPrivate::value() and v4Value().
163// Changes made here may need to be made there and vice versa.
164void QQmlBoundSignalExpression::evaluate(void **a)
165{
166 if (!expressionFunctionValid())
167 return;
168
169 QQmlEngine *qmlengine = engine();
170
171 // If there is no engine, we have no way to evaluate anything.
172 // This can happen on destruction.
173 if (!qmlengine)
174 return;
175
176 QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlengine);
177 QV4::ExecutionEngine *v4 = qmlengine->handle();
178 QV4::Scope scope(v4);
179
180 ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation.
181
182 if (a) {
183 //TODO: lookup via signal index rather than method index as an optimization
184 const QMetaObject *targetMeta = m_target->metaObject();
185 const QMetaMethod metaMethod = targetMeta->method(
186 QMetaObjectPrivate::signal(targetMeta, m_index).methodIndex());
187
188 int argCount = metaMethod.parameterCount();
189 QQmlMetaObject::ArgTypeStorage<9> storage;
190 storage.reserve(argCount + 1);
191 storage.append(QMetaType()); // We're not interested in the return value
192 for (int i = 0; i < argCount; ++i) {
193 const QMetaType type = metaMethod.parameterMetaType(i);
194 if (!type.isValid())
195 argCount = 0;
196 else if (type.flags().testFlag(QMetaType::IsEnumeration))
197 storage.append(type.underlyingType());
198 else
199 storage.append(type);
200 }
201
202 QQmlJavaScriptExpression::evaluate(a, storage.constData(), argCount);
203 } else {
204 void *ignoredResult = nullptr;
205 QMetaType invalidType;
206 QQmlJavaScriptExpression::evaluate(&ignoredResult, &invalidType, 0);
207 }
208
209 ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete.
210}
211
212////////////////////////////////////////////////////////////////////////
213
214
215/*! \internal
216 \a signal MUST be in the signal index range (see QObjectPrivate::signalIndex()).
217 This is different from QMetaMethod::methodIndex().
218*/
219QQmlBoundSignal::QQmlBoundSignal(QObject *target, int signal, QObject *owner,
220 QQmlEngine *engine)
221 : QQmlNotifierEndpoint(QQmlNotifierEndpoint::QQmlBoundSignal),
222 m_prevSignal(nullptr), m_nextSignal(nullptr),
223 m_enabled(true)
224{
225 addToObject(owner);
226
227 /*
228 If this is a cloned method, connect to the 'original'. For example,
229 for the signal 'void aSignal(int parameter = 0)', if the method
230 index refers to 'aSignal()', get the index of 'aSignal(int)'.
231 This ensures that 'parameter' will be available from QML.
232 */
233 signal = QQmlPropertyCache::originalClone(target, signal);
234 QQmlNotifierEndpoint::connect(target, signal, engine);
235}
236
237QQmlBoundSignal::~QQmlBoundSignal()
238{
239 removeFromObject();
240}
241
242void QQmlBoundSignal::addToObject(QObject *obj)
243{
244 Q_ASSERT(!m_prevSignal);
245 Q_ASSERT(obj);
246
247 QQmlData *data = QQmlData::get(obj, true);
248
249 m_nextSignal = data->signalHandlers;
250 if (m_nextSignal) m_nextSignal->m_prevSignal = &m_nextSignal;
251 m_prevSignal = &data->signalHandlers;
252 data->signalHandlers = this;
253}
254
255void QQmlBoundSignal::removeFromObject()
256{
257 if (m_prevSignal) {
258 *m_prevSignal = m_nextSignal;
259 if (m_nextSignal) m_nextSignal->m_prevSignal = m_prevSignal;
260 m_prevSignal = nullptr;
261 m_nextSignal = nullptr;
262 }
263}
264
265/*!
266 Returns the signal expression.
267*/
268QQmlBoundSignalExpression *QQmlBoundSignal::expression() const
269{
270 return m_expression.data();
271}
272
273/*!
274 Sets the signal expression to \a e.
275
276 The QQmlBoundSignal instance takes ownership of \a e (and does not add a reference).
277*/
278void QQmlBoundSignal::takeExpression(QQmlBoundSignalExpression *e)
279{
280 m_expression.adopt(e);
281 if (m_expression)
282 m_expression->setNotifyOnValueChanged(false);
283}
284
285/*!
286 This property holds whether the item will emit signals.
287
288 The QQmlBoundSignal callback will only emit a signal if this property is set to true.
289
290 By default, this property is true.
291 */
292void QQmlBoundSignal::setEnabled(bool enabled)
293{
294 if (m_enabled == enabled)
295 return;
296
297 m_enabled = enabled;
298}
299
300void QQmlBoundSignal_callback(QQmlNotifierEndpoint *e, void **a)
301{
302 QQmlBoundSignal *s = static_cast<QQmlBoundSignal*>(e);
303
304 if (!s->m_expression || !s->m_enabled)
305 return;
306
307 QV4DebugService *service = QQmlDebugConnector::service<QV4DebugService>();
308 if (service)
309 service->signalEmitted(QString::fromUtf8(QMetaObjectPrivate::signal(
310 s->m_expression->target()->metaObject(),
311 s->signalIndex()).methodSignature()));
312
313 QQmlEngine *engine;
314 if (s->m_expression && (engine = s->m_expression->engine())) {
315 Q_TRACE_SCOPE(QQmlHandlingSignal, engine,
316 s->m_expression->function() ? s->m_expression->function()->name()->toQString() : QString(),
317 s->m_expression->sourceLocation().sourceFile, s->m_expression->sourceLocation().line,
318 s->m_expression->sourceLocation().column);
319 QQmlHandlingSignalProfiler prof(QQmlEnginePrivate::get(engine)->profiler,
320 s->m_expression.data());
321 s->m_expression->evaluate(a);
322 if (s->m_expression && s->m_expression->hasError()) {
323 QQmlEnginePrivate::warning(engine, s->m_expression->error(engine));
324 }
325 }
326}
327
328////////////////////////////////////////////////////////////////////////
329
330QQmlPropertyObserver::QQmlPropertyObserver(QQmlBoundSignalExpression *expr)
332 auto This = static_cast<QQmlPropertyObserver*>(self);
333 This->expression->evaluate(nullptr);
334 })
335{
336 expression.adopt(expr);
337}
338
339QT_END_NAMESPACE
QQmlPropertyObserver(QQmlBoundSignalExpression *expr)
Combined button and popup list for selecting options.
void QQmlBoundSignal_callback(QQmlNotifierEndpoint *e, void **a)