LCOV - code coverage report
Current view: top level - interfaceframework - qifpendingreply.cpp (source / functions) Coverage Total Hit
Test: QtInterfaceFramework Lines: 96.1 % 154 148
Test Date: 2024-12-07 10:04:52 Functions: 100.0 % 26 26
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 72.1 % 86 62

             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"
        

Generated by: LCOV version 2.0-1