Branch data Line data Source code
1 : : // Copyright (C) 2021 The Qt Company Ltd.
2 : : // Copyright (C) 2019 Luxoft Sweden AB
3 : : // Copyright (C) 2018 Pelagicore AG
4 : : // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
5 : :
6 : : #include "qifpendingreply.h"
7 : : #include "qifpendingreply_p.h"
8 : : #include "qifqmlconversion_helper.h"
9 : :
10 : : #include "private/qjsengine_p.h"
11 : : #include "private/qjsvalue_p.h"
12 : :
13 : : #include <QDebug>
14 : : #include <QJSEngine>
15 : : #include <QtQml>
16 : : #include <QCoreApplication>
17 : :
18 : : using namespace Qt::StringLiterals;
19 : :
20 : : QT_BEGIN_NAMESPACE
21 : :
22 : : #define QTIF_ADD_STATIC_METATYPE2(MetaTypeName, MetaTypeId, AliasingType, RealName) \
23 : : qIfRegisterPendingReplyType<AliasingType>(RealName);
24 : :
25 : : #define QTIF_ADD_STATIC_METATYPE(MetaTypeName, MetaTypeId, AliasingType) \
26 : : QTIF_ADD_STATIC_METATYPE2(MetaTypeName, MetaTypeId, AliasingType, nullptr)
27 : :
28 : :
29 : : /*!
30 : : \relates QIfPendingReply
31 : :
32 : : Registers QIfPendingReplys of all Qt basic types to the meta type system.
33 : :
34 : : Usually this function called automatically when creating a QCoreApplication or a QIfPendingReply
35 : : and doesn't need to be called manually.
36 : : */
37 : 452 : void qifRegisterPendingReplyBasicTypes() {
38 : 452 : static bool once = false;
39 [ + + ]: 452 : if (once)
40 : : return;
41 : :
42 : : // This function is registered as Q_COREAPP_STARTUP_FUNCTION, which makes sure
43 : : // it is run after the QCoreApplication constructor to ensure we can register
44 : : // types.
45 : : // In case the library is loaded at runtime (because of a qml plugin dependency),
46 : : // the init function would be registered and executed right away before the
47 : : // rest of the library is initialized (e.g. the QMetaObject of QIfPendingReplyBase).
48 : : // The singleshot timer makes sure the registration is done in the next event
49 : : // loop run, when everything is ready.
50 : 81 : QMetaObject::invokeMethod(qApp, []() {
51 : 81 : qRegisterMetaType<QIfPendingReplyBase>("QIfPendingReplyBase");
52 : 81 : QT_FOR_EACH_STATIC_PRIMITIVE_TYPE(QTIF_ADD_STATIC_METATYPE)
53 : 81 : QT_FOR_EACH_STATIC_PRIMITIVE_POINTER(QTIF_ADD_STATIC_METATYPE)
54 : 81 : QT_FOR_EACH_STATIC_CORE_POINTER(QTIF_ADD_STATIC_METATYPE)
55 : 81 : QT_FOR_EACH_STATIC_CORE_TEMPLATE(QTIF_ADD_STATIC_METATYPE)
56 : 81 : QT_FOR_EACH_STATIC_CORE_CLASS(QTIF_ADD_STATIC_METATYPE)
57 : 81 : QT_FOR_EACH_STATIC_ALIAS_TYPE(QTIF_ADD_STATIC_METATYPE2)
58 : 81 : });
59 : 81 : once = true;
60 : : }
61 : :
62 : : Q_COREAPP_STARTUP_FUNCTION(qifRegisterPendingReplyBasicTypes)
63 : :
64 : : // TODO make it reentrant
65 : :
66 : 355 : QIfPendingReplyWatcherPrivate::QIfPendingReplyWatcherPrivate(int userType, QIfPendingReplyWatcher *parent)
67 : : : QObjectPrivate()
68 : 355 : , q_ptr(parent)
69 : 355 : , m_type(userType)
70 : 355 : , m_resultAvailable(false)
71 : 355 : , m_success(false)
72 : 355 : , m_callbackEngine(nullptr)
73 : : {
74 : :
75 : 355 : }
76 : :
77 : 215 : void QIfPendingReplyWatcherPrivate::setSuccess(const QVariant &value)
78 : : {
79 [ + + ]: 215 : Q_Q(QIfPendingReplyWatcher);
80 : :
81 [ + + ]: 215 : if (m_resultAvailable) {
82 : 1 : qWarning("Result is already set. Ignoring request");
83 : 1 : return;
84 : : }
85 : :
86 : 214 : m_resultAvailable = true;
87 : 214 : m_data = value;
88 : 214 : m_success = true;
89 : 214 : emit q->valueChanged(value);
90 : 214 : emit q->replySuccess();
91 : :
92 : 214 : callSuccessCallback();
93 : : }
94 : :
95 : 219 : void QIfPendingReplyWatcherPrivate::callSuccessCallback()
96 : : {
97 [ + + + - ]: 219 : if (!m_successFunctor.isUndefined() && m_callbackEngine) {
98 : 32 : QJSValueList list = { m_callbackEngine->toScriptValue(m_data) };
99 : 16 : m_successFunctor.call(list);
100 : 16 : }
101 : 219 : }
102 : :
103 : 114 : void QIfPendingReplyWatcherPrivate::callFailedCallback()
104 : : {
105 [ + + + - ]: 114 : if (!m_failedFunctor.isUndefined() && m_callbackEngine)
106 : 11 : m_failedFunctor.call();
107 : :
108 [ + + ]: 114 : if (m_failedFunctor.isUndefined() && !m_successFunctor.isUndefined()) {
109 : : //Logging category for logging unhandled failed functor with location.
110 : : }
111 : 114 : }
112 : :
113 : : /*!
114 : : \class QIfPendingReplyWatcher
115 : : \inmodule QtInterfaceFramework
116 : : \brief The QIfPendingReplyWatcher provides signals for QIfPendingReply.
117 : :
118 : : The QIfPendingReplyWatcher holds all data of a QIfPendingReply and is implicitly shared
119 : : between copies of the same QIfPendingReply instance. At the same time the watcher provides
120 : : signals for when a result is ready or an error happened.
121 : :
122 : : A QIfPendingReplyWatcher cannot be instantiated on its own. It is always created from a
123 : : QIfPendingReply internally.
124 : : */
125 : :
126 : : /*!
127 : : \class QIfPendingReplyBase
128 : : \inmodule QtInterfaceFramework
129 : : \brief The QIfPendingReplyBase is the base class for QIfPendingReply.
130 : :
131 : : QIfPendingReplyBase is the base class for QIfPendingReply and provides QVariant based
132 : : functions and properties for the usage from QML.
133 : :
134 : : Usually you don't have to use this class, but instead always use the typesafe QIfPendingReply
135 : : template class.
136 : : */
137 : :
138 : : /*!
139 : : \qmltype PendingReply
140 : : \qmlabstract
141 : : \nativetype QIfPendingReply
142 : : \inqmlmodule QtInterfaceFramework
143 : :
144 : : \brief An object representing asynchronous results.
145 : :
146 : : A PendingReply is a way for providing asynchronous results. It can be used as a
147 : : return value for asynchronous functions.
148 : :
149 : : The QML API is very similar to
150 : : \l{https://developers.google.com/web/fundamentals/primers/promises}{JavaScript Promises}.
151 : :
152 : : This documentation shows how to use the PendingReply from within QML and how to execute code
153 : : once the asynchronous result is ready.
154 : :
155 : : \note It is not supported to create a PendingReply from QML. The object is supposed to be
156 : : created from C++ and returned to QML as a result. For more information on how to use it from
157 : : C++ see the \l {QIfPendingReply}{QIfPendingReply documentation}.
158 : :
159 : : When a PendingReply is created from C++ it doesn't have a result set yet and the
160 : : \l{PendingReply::}{resultAvailable} property is \c false. A result for the pending reply can
161 : : only be set once and it either indicates a failed(setFailed) or a successful(setSuccess) call.
162 : : This can be checked using the \l{PendingReply::}{success} property. The actual result is
163 : : available from the \l{PendingReply::}{value} property, which returns undefined if no result is
164 : : available or the reply failed.
165 : :
166 : : \section1 Using a PendingReply
167 : :
168 : : As explained above, the PendingReply is supposed to be used as a return value for asynchronous
169 : : operations done in C++. To inform about when a reply result is available there are two ways:
170 : :
171 : : \section2 The \e then method
172 : :
173 : : Similar to a JavaScript Promise the PendingReply is then-able, which means it provides a \e then
174 : : method. This method can be used to add callbacks which are executed when the reply succeeds or
175 : : fails.
176 : :
177 : : \qml
178 : : import QtQuick
179 : : import QtInterfaceFramework
180 : :
181 : : Text {
182 : : id: root
183 : : text: "not ready"
184 : :
185 : : Component.onCompleted: {
186 : : var asyncReply = TestObject.asyncFunction();
187 : : asyncReply.then(function(value) {
188 : : root.text = "reply ready: " + value
189 : : },
190 : : function() {
191 : : root.text = "reply failed"
192 : : })
193 : : }
194 : : }
195 : : \endqml
196 : :
197 : : This simple QML snippet calls the C++ function TestObject::asyncFunction() which returns a
198 : : PendingReply. See the \l{QIfPendingReply}{C++ part} on how to write such a function and use
199 : : the PendingReply from C++.
200 : :
201 : : The then method is used to register two callbacks. The first callback is the result callback
202 : : and takes the reply value as an argument. This will update the text element accordingly. The
203 : : second argument is the failed callback, which doesn't take an argument as there is no valid
204 : : reply value.
205 : :
206 : : If the failed state is not of interest it is also possible to not add any callback for it e.g.
207 : : \qml
208 : : asyncReply.then(function(value) {
209 : : root.text = "reply ready: " + value
210 : : })
211 : : \endqml
212 : :
213 : : In a similar way only the failed callback can be registered by passing \e undefined to the
214 : : function as the first argument:
215 : :
216 : : \qml
217 : : asyncReply.then(undefined,
218 : : function() {
219 : : root.text = "reply failed"
220 : : })
221 : : \endqml
222 : :
223 : : \section3 Synchronous results
224 : :
225 : : When a PendingReply object is used in an API the corresponding function cannot provide the
226 : : result immediately. But especially for input validation the function can return an error state
227 : : right away. For this the PendingReply object offers the properties
228 : : \l{PendingReply::}{resultAvailable} and \l{PendingReply::}{success} to check for this when the
229 : : object is given for QML.
230 : :
231 : : Checking this for every PendingReply use in QML can be tedious and produces a lot of
232 : : boiler-plate code. Because of this the PendingReply works similar to a JavaScript Promise and
233 : : will execute the callbacks although the result is already available.
234 : :
235 : : \section2 Signals and Slots
236 : :
237 : : Although the then method is the recommended way from QML, the PendingReply also provides
238 : : signals. To make the PendingReply as lightweight as possible it is using Q_GADGET and cannot
239 : : provide signals directly, but provides it through the QIfPendingReplyWatcher class. The
240 : : QIfPendingReplyWatcher can be accessed using the \l{PendingReply::}{watcher} property.
241 : :
242 : : \note The QIfPendingReplyWatcher is owned by the PendingReply. Saving the watcher outside of
243 : : of the PendingReply is not safe as it is destroyed once all copies of this PendingReply object
244 : : are destroyed.
245 : : */
246 : :
247 : : /*!
248 : : \class QIfPendingReply
249 : : \inmodule QtInterfaceFramework
250 : : \brief Template class for providing asynchronous results.
251 : :
252 : : A QIfPendingReply is a template class for providing asynchronous results. It can be used as a
253 : : return value for asynchronous functions, similar to QFuture.
254 : :
255 : : In contrast to QFuture, QIfPendingReply works also in QML and is especially made for this. The
256 : : data stored in a QIfPendingReply is implicitly shared between all copies of this reply object.
257 : : This keeps the memory and performance footprint low.
258 : :
259 : : The QML API is very similar to
260 : : \l{https://developers.google.com/web/fundamentals/primers/promises}{JavaScript Promises}, at
261 : : the same time the C++ API provides support for Qt's signals and slots.
262 : :
263 : : The QIfPendingReply holds a result of a specific type. The type needs to have a default
264 : : constructor and a copy constructor. By default the most Qt basic types are supported. New types
265 : : can be added by using the qIfRegisterPendingReplyType function.
266 : :
267 : : When a QIfPendingReply is created it does not have a valid result set yet. This can be checked
268 : : by using the resultAvailable property. A result for a reply can be set by using the setFailed
269 : : or setSuccess functions. Setting the result with this function can only be done once and cannot
270 : : be changed later. Whether a QIfPendingReply has succeeded can be determined by the success
271 : : property.
272 : :
273 : : \section1 Writing a function returning a QIfPendingReply
274 : :
275 : : When writing a function returning a QIfPendingReply, it is often needed to do some input
276 : : validation and return before actual doing something. Without using a QIfPendingReply one would
277 : : write a function as follows:
278 : :
279 : : \code
280 : : QString displayName(const QUuid &id)
281 : : {
282 : : if (id.isNull)
283 : : return QString();
284 : :
285 : : //do something and wait until the result is ready (synchronous)
286 : : asyncAPI.getDisplayName(id);
287 : : asyncAPI.waitForFinished(&displayNameChanged);
288 : : return asyncAPI.displayName();
289 : : }
290 : : \endcode
291 : :
292 : : This function is using an asynchronous API e.g. provided by an IPC. getDisplayName(id) starts
293 : : the task and once a result is ready the displayNameChanged signal is emitted and the actual
294 : : value can be read using the displayName() function. The provided function is using a
295 : : waitForFinished() method to actual wait for the signal to be emitted and return the value and
296 : : make this API synchronous.
297 : :
298 : : When moving this code to using QIfPendingReply the validation check needs to be fixed to
299 : : return a valid QIfPendingReply. To make it more convenient to return a failed reply, the
300 : : QIfPendingReply::createFailedReply() function be used.
301 : :
302 : : Rewriting the above function to be fully asynchronous using a QIfPendingReply it would look
303 : : like this:
304 : :
305 : : \code
306 : : QIfPendingReply<QString> displayName(const QUuid &id)
307 : : {
308 : : if (id.isNull)
309 : : return QIfPendingReply<QString>::createFailedReply();
310 : :
311 : : QIfPendingReply<QString> reply
312 : : //connect to the change signal and set the result to the async reply when ready
313 : : connect(asyncAPI, &displayNameChanged, this, [reply, asyncAPI]() mutable {
314 : : reply.setSuccess(asyncAPI.displayName());
315 : : });
316 : : //start getting the name
317 : : asyncAPI.getDisplayName(id);
318 : : return reply;
319 : : }
320 : : \endcode
321 : :
322 : : Now a new QIfPendingReply is created right away and passed to the lamda used in the connect
323 : : statement. The actual task is started afterwards and the reply object is returned. Once the
324 : : async API emits the displayNameChanged signal the lamda is executed the QIfPendingReply is
325 : : marked as successful and the value set to the displayName().
326 : :
327 : : \note All copies of a QIfPendingReply use implicit sharing. This data is freed once all copies
328 : : of the pending replies are deleted.
329 : :
330 : : \section1 Using functions returning a QIfPendingReply
331 : :
332 : : When using a function which returns a QIfPendingReply, the first thing to do is to check
333 : : whether a result is already available using the isResultAvailable property and act accordingly.
334 : : Afterwards you can start to connect the signals provided by the QIfPendingReplyWatcher.
335 : :
336 : : \section2 Signals and Slots
337 : :
338 : : In order to keep the memory footprint low, the QIfPendingReply doesn't provide signals
339 : : directly, as it doesn't need to derive from QObject, but uses the Q_GADGET macro instead. To
340 : : get notified once a result is ready, the QIfPendingReplyWatcher can be used instead. The
341 : : watcher can be retrieved using the watcher property.
342 : :
343 : : Here an example on how this would work when using the API described above:
344 : :
345 : : \code
346 : : QUuid uuid = createUuid();
347 : : QIfPendingReply<QString> reply = displayName(uuid);
348 : : if (reply.isResultAvailable()) {
349 : : if (reply.isSuccessfull())
350 : : useDisplayName(reply.value());
351 : : else
352 : : qWarning("getting the displayName failed");
353 : : } else {
354 : : connect(reply.watcher(), &QIfPendingReplyWatcher::valueChanged, this, [this, reply]() {
355 : : if (reply.isSuccessfull())
356 : : useDisplayName(reply.value());
357 : : else
358 : : qWarning("getting the displayName failed");
359 : : });
360 : : }
361 : : \endcode
362 : :
363 : : As described above, the pending reply is checked first for whether a result is already available
364 : : and if not, the signals from the watcher are used to react to the valueChanged signal.
365 : :
366 : : \note The QIfPendingReplyWatcher returned is owned by the QIfPendingReply and all its
367 : : copies. If all copies of the QIfPendingReply get deleted its QIfPendingReplyWatcher gets
368 : : deleted as well.
369 : :
370 : : For usage in QML see the QML documentation.
371 : : */
372 : :
373 : 355 : QIfPendingReplyWatcher::QIfPendingReplyWatcher(int userType)
374 : 355 : : QObject(*new QIfPendingReplyWatcherPrivate(userType, this))
375 : : {
376 : 355 : }
377 : :
378 : : /*!
379 : : \property QIfPendingReplyWatcher::value
380 : : \brief Holds the current value of the QIfPendingReply
381 : :
382 : : If no result is available yet or the reply failed, a default constructed QVariant() is returned.
383 : : Otherwise a QVariant holding the result is returned.
384 : : */
385 : 302 : QVariant QIfPendingReplyWatcher::value() const
386 : : {
387 : 302 : Q_D(const QIfPendingReplyWatcher);
388 : 302 : return d->m_data;
389 : : }
390 : :
391 : : /*!
392 : : \property QIfPendingReplyWatcher::valid
393 : : \brief Holds whether the watcher is valid
394 : :
395 : : A watcher can be invalid if a QIfPendingReplyBase is manually created not using the template
396 : : class QIfPendingReply.
397 : : */
398 : 183 : bool QIfPendingReplyWatcher::isValid() const
399 : : {
400 : 183 : Q_D(const QIfPendingReplyWatcher);
401 : 183 : return d->m_type != -1;
402 : : }
403 : :
404 : : /*!
405 : : \property QIfPendingReplyWatcher::resultAvailable
406 : : \brief Holds whether a result has been set
407 : :
408 : : This property is \c true once a result has been set by using setSuccess() or setFailed().
409 : : */
410 : 467 : bool QIfPendingReplyWatcher::isResultAvailable() const
411 : : {
412 : 467 : Q_D(const QIfPendingReplyWatcher);
413 : 467 : return d->m_resultAvailable;
414 : : }
415 : :
416 : : /*!
417 : : \property QIfPendingReplyWatcher::success
418 : : \brief Holds whether the reply succeeded
419 : :
420 : : This property is \c true if the reply has a valid result set by calling setSuccess().
421 : : */
422 : 264 : bool QIfPendingReplyWatcher::isSuccessful() const
423 : : {
424 : 264 : Q_D(const QIfPendingReplyWatcher);
425 : 264 : return d->m_success;
426 : : }
427 : :
428 : : /*!
429 : : Sets the result of the reply to \a value and marks the reply as succeeded.
430 : :
431 : : The given value needs to be of the same type than the reply or convertible to that type.
432 : :
433 : : \note a result can only be set once and cannot be changed again later.
434 : :
435 : : \sa setFailed
436 : : */
437 : 181 : void QIfPendingReplyWatcher::setSuccess(const QVariant &value)
438 : : {
439 [ - + ]: 181 : Q_D(QIfPendingReplyWatcher);
440 : :
441 [ - + ]: 181 : if (d->m_resultAvailable) {
442 : 0 : qtif_qmlOrCppWarning(this, "Result is already set. Ignoring request");
443 : 35 : return;
444 : : }
445 : :
446 : : //no type checking needed when we expect a QVariant or void
447 [ + + ]: 181 : if (d->m_type == qMetaTypeId<QVariant>() || d->m_type == qMetaTypeId<void>()) {
448 : 33 : d->setSuccess(value);
449 : 33 : return;
450 : : }
451 : :
452 : 148 : QVariant var = value;
453 : :
454 : : //We need a special conversion for enums from QML as they are saved as int
455 : 148 : QMetaType metaType(d->m_type);
456 : 148 : bool isEnumOrFlag = false;
457 : :
458 : : //Try to convert the value, if successfully, use the converted value
459 : 148 : QVariant temp(var);
460 [ + + ]: 148 : if (temp.convert(metaType))
461 : 147 : var = temp;
462 : :
463 : :
464 : 148 : const QMetaObject *mo = metaType.metaObject();
465 [ + - + - ]: 296 : const QString enumName = QString::fromLocal8Bit(metaType.name()).split(u"::"_s).last();
466 [ + + ]: 148 : if (mo) {
467 [ + - ]: 98 : QMetaEnum mEnum = mo->enumerator(mo->indexOfEnumerator(enumName.toLocal8Bit().constData()));
468 [ + + ]: 49 : if (mEnum.isValid()) {
469 : 17 : isEnumOrFlag = true;
470 [ + - + + ]: 17 : if (!mEnum.isFlag() && !mEnum.valueToKey(var.toInt())) {
471 : 1 : qtif_qmlOrCppWarning(this, "Enum value out of range");
472 : 1 : return;
473 : : }
474 : : }
475 : : }
476 : :
477 : : //Check that the type names match only if it's not a enum, as it will be converted automatically in this case.
478 [ + + ]: 147 : if (!isEnumOrFlag && var.metaType() != metaType) {
479 [ - + + - : 3 : qtif_qmlOrCppWarning(this, QString(u"Expected: %1 but got %2"_s).arg(QLatin1String(metaType.name()), QLatin1String(var.metaType().name())));
- + + - ]
480 : 1 : return;
481 : : }
482 : :
483 : 146 : d->setSuccess(var);
484 : 148 : }
485 : :
486 : : /*!
487 : : Marks the reply as failed.
488 : :
489 : : \note a result can only be set once and cannot be changed again later.
490 : :
491 : : \sa setSuccess
492 : : */
493 : 111 : void QIfPendingReplyWatcher::setFailed()
494 : : {
495 [ - + ]: 111 : Q_D(QIfPendingReplyWatcher);
496 [ - + ]: 111 : if (d->m_resultAvailable) {
497 : 0 : qWarning("Result is already set. Ignoring request");
498 : 0 : return;
499 : : }
500 : :
501 : 111 : d->m_resultAvailable = true;
502 : : //emitting valueChanged is intended here as it makes it easier to react to successful and failed
503 : : //replies in the same slot.
504 : 111 : emit valueChanged(d->m_data);
505 : 111 : emit replyFailed();
506 : :
507 : 111 : d->callFailedCallback();
508 : : }
509 : :
510 : : /*!
511 : : Sets the JavaScript callbacks to be called once a result is delivered. If the reply succeeded
512 : : the \a success callback is called, otherwise the \a failed callback.
513 : :
514 : : The \a success callback can take the reply value as an argument.
515 : :
516 : : The provided values need to be \l {QJSValue::isCallable}{callable} and constructed from a
517 : : QJSEngine. Passing QJSValue objects created by C++ will result in an error.
518 : :
519 : : Calling this function multiple times will override the existing callbacks.
520 : : */
521 : 29 : void QIfPendingReplyWatcher::then(const QJSValue &success, const QJSValue &failed)
522 : : {
523 [ + + + + ]: 29 : if (!success.isUndefined() && !success.isCallable()) {
524 : 1 : qtif_qmlOrCppWarning(this, "The success functor is not callable");
525 : 1 : return;
526 : : }
527 : :
528 [ + + + + ]: 28 : if (!failed.isUndefined() && !failed.isCallable()) {
529 : 1 : qtif_qmlOrCppWarning(this, "The failed functor is not callable");
530 : 1 : return;
531 : : }
532 : :
533 : 27 : Q_D(QIfPendingReplyWatcher);
534 : 27 : d->m_successFunctor = success;
535 : 27 : d->m_failedFunctor = failed;
536 [ - + ]: 27 : d->m_callbackEngine = QJSValuePrivate::engine(&d->m_successFunctor)->jsEngine();
537 [ - + ]: 27 : if (!d->m_callbackEngine)
538 : 0 : d->m_callbackEngine = QJSValuePrivate::engine(&d->m_failedFunctor)->jsEngine();
539 : :
540 [ - + ]: 27 : if (!d->m_callbackEngine)
541 : 0 : qtif_qmlOrCppWarning(this, "Couldn't access the current QJSEngine. The given callbacks will not be called without a valid QJSEngine");
542 : :
543 [ + + ]: 27 : if (d->m_resultAvailable) {
544 [ + + ]: 8 : if (d->m_success)
545 : 5 : d->callSuccessCallback();
546 : : else
547 : 3 : d->callFailedCallback();
548 : : }
549 : : }
550 : :
551 : 355 : QIfPendingReplyBase::QIfPendingReplyBase(int userType)
552 : 355 : : m_watcher(new QIfPendingReplyWatcher(userType))
553 : : {
554 : 355 : qifRegisterPendingReplyBasicTypes();
555 : 355 : }
556 : :
557 : 552 : QIfPendingReplyBase::QIfPendingReplyBase(const QIfPendingReplyBase &other)
558 : : {
559 : 552 : this->m_watcher = other.m_watcher;
560 : 552 : }
561 : :
562 : 377 : QIfPendingReplyBase::QIfPendingReplyBase(const QIfPendingReplyBase && other)
563 : : {
564 : 377 : this->m_watcher = std::move(other.m_watcher);
565 : 377 : }
566 : :
567 : : /*!
568 : : \qmlproperty QIfPendingReplyWatcher* PendingReply::watcher
569 : : \brief Holds the watcher for the PendingReply
570 : :
571 : : \note The QIfPendingReplyWatcher returned is owned by the PendingReply and all its copies. If
572 : : all copies of the PendingReply get deleted its QIfPendingReplyWatcher gets deleted as well.
573 : : */
574 : : /*!
575 : : \property QIfPendingReplyBase::watcher
576 : : \brief Holds the watcher for the QIfPendingReply
577 : :
578 : : \note The QIfPendingReplyWatcher returned is owned by the QIfPendingReply and all its
579 : : copies. If all copies of the QIfPendingReply get deleted its QIfPendingReplyWatcher gets
580 : : deleted as well.
581 : : */
582 : 574 : QIfPendingReplyWatcher *QIfPendingReplyBase::watcher() const
583 : : {
584 : 574 : return m_watcher.data();
585 : : }
586 : :
587 : : /*!
588 : : \qmlproperty var PendingReply::value
589 : : \brief Holds the current value of the PendingReply
590 : :
591 : : If no result is available yet or the reply failed, a default constructed QVariant() is returned.
592 : : Otherwise a QVariant holding the result is returned.
593 : : */
594 : : /*!
595 : : \property QIfPendingReplyBase::value
596 : : \brief Holds the current value of the QIfPendingReply
597 : :
598 : : If no result is available yet or the reply failed, a default constructed QVariant() is returned.
599 : : Otherwise a QVariant holding the result is returned.
600 : : */
601 : 93 : QVariant QIfPendingReplyBase::value() const
602 : : {
603 [ + - ]: 93 : if (m_watcher)
604 : 93 : return m_watcher->value();
605 : 0 : return QVariant();
606 : : }
607 : :
608 : : /*!
609 : : \qmlproperty bool PendingReply::valid
610 : : \brief Holds whether the PendingReply is valid
611 : :
612 : : A watcher can be invalid if a PendingReply is manually created not using the template
613 : : class QIfPendingReply.
614 : : */
615 : : /*!
616 : : \property QIfPendingReplyBase::valid
617 : : \brief Holds whether the QIfPendingReplyBase is valid
618 : :
619 : : A watcher can be invalid if a QIfPendingReplyBase is manually created not using the template
620 : : class QIfPendingReply.
621 : : */
622 : 146 : bool QIfPendingReplyBase::isValid() const
623 : : {
624 [ + + ]: 146 : if (m_watcher)
625 : 145 : return m_watcher->isValid();
626 : : return false;
627 : : }
628 : :
629 : : /*!
630 : : \qmlproperty bool PendingReply::resultAvailable
631 : : \brief Holds whether a result has been set
632 : :
633 : : This property is \c true once a result has been set by using setSuccess() or setFailed().
634 : : */
635 : : /*!
636 : : \property QIfPendingReplyBase::resultAvailable
637 : : \brief Holds whether a result has been set
638 : :
639 : : This property is \c true once a result has been set by using setSuccess() or setFailed().
640 : : */
641 : 429 : bool QIfPendingReplyBase::isResultAvailable() const
642 : : {
643 [ + - ]: 429 : if (m_watcher)
644 : 429 : return m_watcher->isResultAvailable();
645 : : return false;
646 : : }
647 : :
648 : : /*!
649 : : \qmlproperty bool PendingReply::success
650 : : \brief Holds whether the reply succeeded
651 : :
652 : : This property is \c true if the reply has a valid result set by calling setSuccess().
653 : : */
654 : : /*!
655 : : \property QIfPendingReplyBase::success
656 : : \brief Holds whether the reply succeeded
657 : :
658 : : This property is \c true if the reply has a valid result set by calling setSuccess().
659 : : */
660 : 218 : bool QIfPendingReplyBase::isSuccessful() const
661 : : {
662 [ + - ]: 218 : if (m_watcher)
663 : 218 : return m_watcher->isSuccessful();
664 : : return false;
665 : : }
666 : :
667 : : /*!
668 : : \qmlmethod PendingReply::then(success, failed)
669 : :
670 : : Sets the JavaScript callbacks to be called once a result is delivered. If the reply succeeded
671 : : the \a success callback is called, otherwise the \a failed callback.
672 : :
673 : : The \a success callback can take the reply value as an argument.
674 : :
675 : : See \l{PendingReply#The then method}{The \e then method} for example usage.
676 : :
677 : : Calling this function multiple times will override the existing callbacks.
678 : : */
679 : : /*!
680 : : Sets the JavaScript callbacks to be called once a result is delivered. If the reply succeeded
681 : : the \a success callback is called, otherwise the \a failed callback.
682 : :
683 : : The \a success callback can take the reply value as an argument.
684 : :
685 : : The provided values need to be \l {QJSValue::isCallable}{callable} and constructed from a
686 : : QJSEngine. Passing QJSValue objects created by C++ will result in an error.
687 : :
688 : : Calling this function multiple times will override the existing callbacks.
689 : : */
690 : 29 : void QIfPendingReplyBase::then(const QJSValue &success, const QJSValue &failed)
691 : : {
692 [ + - ]: 29 : if (m_watcher)
693 : 29 : m_watcher->then(success, failed);
694 : 29 : }
695 : :
696 : : /*!
697 : : \qmlmethod PendingReply::setSuccess(var value)
698 : :
699 : : Sets the result of the reply to \a value and marks the reply as succeeded.
700 : :
701 : : The given value needs to be of the same type as the reply or be convertible to that type.
702 : :
703 : : \note a result can only be set once and cannot be changed again later.
704 : :
705 : : \sa setFailed
706 : : */
707 : : /*!
708 : : Sets the result of the reply to \a value and marks the reply as succeeded.
709 : :
710 : : The given value needs to be of the same type as the reply or be convertible to that type.
711 : :
712 : : \note a result can only be set once and cannot be changed again later.
713 : :
714 : : \sa setFailed
715 : : */
716 : 181 : void QIfPendingReplyBase::setSuccess(const QVariant &value)
717 : : {
718 [ + - ]: 181 : if (m_watcher)
719 : 181 : m_watcher->setSuccess(value);
720 : 181 : }
721 : :
722 : : /*!
723 : : \qmlmethod PendingReply::setFailed()
724 : :
725 : : Marks the reply as failed.
726 : :
727 : : \note a result can only be set once and cannot be changed again later.
728 : :
729 : : \sa setSuccess
730 : : */
731 : : /*!
732 : : Marks the reply as failed.
733 : :
734 : : \note a result can only be set once and cannot be changed again later.
735 : :
736 : : \sa setSuccess
737 : : */
738 : 111 : void QIfPendingReplyBase::setFailed()
739 : : {
740 [ + - ]: 111 : if (m_watcher)
741 : 111 : m_watcher->setFailed();
742 : 111 : }
743 : :
744 : : /*!
745 : : \internal
746 : :
747 : : Sets the result of the reply to \a value and marks the reply as succeeded, but without checking
748 : : if the QVariant can be converted. This is used by the template class as we convert it to a
749 : : QVariant before anyway and can be sure the type is correct.
750 : : */
751 : 36 : void QIfPendingReplyBase::setSuccessNoCheck(const QVariant &value)
752 : : {
753 [ + - ]: 36 : if (m_watcher)
754 : 36 : m_watcher->d_func()->setSuccess(value);
755 : 36 : }
756 : :
757 : : /*!
758 : : \fn QIfPendingReplyWatcher::replyFailed()
759 : :
760 : : Emitted when the reply is marked as failed.
761 : :
762 : : \sa setFailed
763 : : */
764 : :
765 : : /*!
766 : : \fn QIfPendingReplyWatcher::replySuccess()
767 : :
768 : : Emitted when the reply is marked as successful.
769 : :
770 : : \sa setSuccess
771 : : */
772 : :
773 : : /*!
774 : : \fn QIfPendingReplyWatcher::valueChanged(const QVariant &value)
775 : :
776 : : Emitted when the result for the reply is ready. This signal is called when the reply is
777 : : successful as well as when it is failed. The \a value argument holds the result and is a default
778 : : constructed QVariant in the failed case.
779 : :
780 : : \sa setSuccess setFailed
781 : : */
782 : :
783 : : /*!
784 : : \fn template <class T> QIfPendingReply<T> QIfPendingReply<T>::createFailedReply()
785 : :
786 : : Creates a reply object which is marked as failed. This is convenient in error cases inside
787 : : functions returning a reply e.g.
788 : :
789 : : \code
790 : : QIfPendingReply<QString> doSomething(int value)
791 : : {
792 : : if (value <= 0) {
793 : : qWarning("The value needs to be bigger than 0");
794 : : return QIfPendingReply<QString>::createFailedReply()
795 : : }
796 : :
797 : : QIfPendingReply<QString> reply;
798 : : ...
799 : : return reply;
800 : : }
801 : : \endcode
802 : : */
803 : :
804 : : /*!
805 : : \fn template <class T> QIfPendingReply<T>::QIfPendingReply(const T &value)
806 : :
807 : : Creates a new QIfPendingReply that stores type T.
808 : : The pending reply is set to successful using \a value.
809 : :
810 : : This is equivalent to:
811 : : \code
812 : : QIfPendingReply<T> reply.
813 : : reply.setSuccess(value);
814 : : \endcode
815 : : */
816 : :
817 : : /*!
818 : : \fn template <class T> T QIfPendingReply<T>::reply() const
819 : :
820 : : Returns the result of the reply. If no result has been set yet or when the reply is marked as
821 : : failed, a default constructed value is returned.
822 : :
823 : : \sa setSuccess setFailed
824 : : */
825 : :
826 : : /*!
827 : : \fn template <class T> void QIfPendingReply<T>::setSuccess(const T &val)
828 : :
829 : : Sets the result of the reply to \a val and marks the reply as succeeded.
830 : :
831 : : \note a result can only be set once and cannot be changed again later.
832 : :
833 : : \sa setFailed
834 : : */
835 : :
836 : : /*!
837 : : \fn template <class T> void QIfPendingReply<T>::then(const std::function<void (const T &)> &success, const std::function<void ()> &failed)
838 : :
839 : : Sets the C++ callbacks to be called once a result is delivered. If the reply succeeds
840 : : \a success is called; otherwise \a failed is called.
841 : :
842 : : The \a success callback gets the reply value as an argument.
843 : :
844 : : In case the result of the pending reply is already available when this function is called, the corresponding callback functions are
845 : : run immediately.
846 : :
847 : : \sa QIfPendingReplyBase::then
848 : : */
849 : :
850 : : /*!
851 : : \fn template <typename T> void qIfRegisterPendingReplyType(const char *name)
852 : : \relates QIfPendingReply
853 : :
854 : : Registers the type name \a name for the type \c{T} for usage inside a QIfPendingReply. Any
855 : : class or struct that has a public default constructor, a public copy constructor and a public
856 : : destructor can be registered.
857 : :
858 : : This function requires that \c{T} is a fully defined type at the point where the function is
859 : : called. For pointer types, it also requires that the pointed-to type is fully defined. Use
860 : : Q_DECLARE_OPAQUE_POINTER() to be able to register pointers to forward declared types.
861 : :
862 : : Please see qRegisterMetaType for more information.
863 : : */
864 : :
865 : : /*!
866 : : \macro QIF_DECLARE_PENDINGREPLY(TYPE)
867 : : \relates QIfPendingReply
868 : : \since 6.8
869 : :
870 : : Declares the type \a TYPE to be used in a QIfPendingReply. This macro is used to make sure
871 : : that QIfPendingReply<TYPE> can be used in QML.
872 : :
873 : : This macro should be used in the header file directly after the definition of the type.
874 : :
875 : : \sa QIF_DECLARE_PENDINGREPLY_WITH_NAME
876 : : */
877 : :
878 : : /*!
879 : : \macro QIF_DECLARE_PENDINGREPLY_WITH_NAME(NAME, TYPE)
880 : : \relates QIfPendingReply
881 : : \since 6.8
882 : :
883 : : Declares the type \a TYPE to be used in a QIfPendingReply. This macro is used to make sure
884 : : that QIfPendingReply<TYPE> can be used in QML.
885 : : The passed \a NAME is part of the struct name used to do the QML registration.
886 : :
887 : : This macro should be used in the header file directly after the definition of the type.
888 : :
889 : : \note Most of the time QIF_DECLARE_PENDINGREPLY should be used instead of this macro. This
890 : : macro is useful for enums or types inside namespaces.
891 : :
892 : : \sa QIF_DECLARE_PENDINGREPLY
893 : : */
894 : :
895 : : QT_END_NAMESPACE
896 : :
897 : : #include "moc_qifpendingreply.cpp"
|