File indexing completed on 2024-05-05 16:07:09

0001 /*
0002     SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "qmlobject.h"
0008 #include "private/kdeclarative_p.h"
0009 
0010 #include <QQmlContext>
0011 #include <QQmlEngine>
0012 #include <QQmlIncubator>
0013 #include <QQmlNetworkAccessManagerFactory>
0014 #include <QQuickItem>
0015 #include <QTimer>
0016 
0017 #if KDECLARATIVE_BUILD_DEPRECATED_SINCE(5, 98)
0018 #include <KPackage/PackageLoader>
0019 #endif
0020 #include <QDebug>
0021 #include <kdeclarative.h>
0022 
0023 //#include "packageaccessmanagerfactory.h"
0024 //#include "private/declarative/dataenginebindings_p.h"
0025 
0026 namespace KDeclarative
0027 {
0028 class QmlObjectIncubator : public QQmlIncubator
0029 {
0030 public:
0031     QVariantHash m_initialProperties;
0032 
0033 protected:
0034     void setInitialState(QObject *object) override
0035     {
0036         QHashIterator<QString, QVariant> i(m_initialProperties);
0037         while (i.hasNext()) {
0038             i.next();
0039             object->setProperty(i.key().toLatin1().data(), i.value());
0040         }
0041     }
0042 };
0043 
0044 class QmlObjectPrivate
0045 {
0046 public:
0047     QmlObjectPrivate(QmlObject *parent)
0048         : q(parent)
0049         , engine(nullptr)
0050         , component(nullptr)
0051         , delay(false)
0052     {
0053         executionEndTimer = new QTimer(q);
0054         executionEndTimer->setInterval(0);
0055         executionEndTimer->setSingleShot(true);
0056         QObject::connect(executionEndTimer, &QTimer::timeout, q, [this]() {
0057             scheduleExecutionEnd();
0058         });
0059     }
0060 
0061     ~QmlObjectPrivate()
0062     {
0063         delete incubator.object();
0064     }
0065 
0066     void errorPrint(QQmlComponent *component);
0067     void execute(const QUrl &source);
0068     void scheduleExecutionEnd();
0069     void minimumWidthChanged();
0070     void minimumHeightChanged();
0071     void maximumWidthChanged();
0072     void maximumHeightChanged();
0073     void preferredWidthChanged();
0074     void preferredHeightChanged();
0075     void checkInitializationCompleted();
0076 
0077     QmlObject *q;
0078 
0079     QUrl source;
0080     std::shared_ptr<QQmlEngine> engine;
0081 
0082     QmlObjectIncubator incubator;
0083     QQmlComponent *component;
0084     QTimer *executionEndTimer;
0085     KLocalizedContext *context{nullptr};
0086 #if KDECLARATIVE_BUILD_DEPRECATED_SINCE(5, 98)
0087     KPackage::Package package;
0088 #endif
0089     QQmlContext *rootContext;
0090     bool delay : 1;
0091 };
0092 
0093 void QmlObjectPrivate::errorPrint(QQmlComponent *component)
0094 {
0095     QString errorStr = QStringLiteral("Error loading QML file.\n");
0096     if (component->isError()) {
0097         const QList<QQmlError> errors = component->errors();
0098         for (const QQmlError &error : errors) {
0099             errorStr +=
0100                 (error.line() > 0 ? QString(QString::number(error.line()) + QLatin1String(": ")) : QLatin1String("")) + error.description() + QLatin1Char('\n');
0101         }
0102     }
0103     qWarning() << component->url().toString() << '\n' << errorStr;
0104 }
0105 
0106 void QmlObjectPrivate::execute(const QUrl &source)
0107 {
0108     if (source.isEmpty()) {
0109         qWarning() << "File name empty!";
0110         return;
0111     }
0112 
0113     delete component;
0114     component = new QQmlComponent(engine.get(), q);
0115     QObject::connect(component, &QQmlComponent::statusChanged, q, &QmlObject::statusChanged, Qt::QueuedConnection);
0116     delete incubator.object();
0117 
0118     component->loadUrl(source);
0119 
0120     if (delay) {
0121         executionEndTimer->start(0);
0122     } else {
0123         scheduleExecutionEnd();
0124     }
0125 }
0126 
0127 void QmlObjectPrivate::scheduleExecutionEnd()
0128 {
0129     if (component->isReady() || component->isError()) {
0130         q->completeInitialization();
0131     } else {
0132         QObject::connect(component, &QQmlComponent::statusChanged, q, [this]() {
0133             q->completeInitialization();
0134         });
0135     }
0136 }
0137 
0138 QmlObject::QmlObject(QObject *parent)
0139     : QmlObject(nullptr, nullptr, parent)
0140 {
0141 }
0142 
0143 #if KDECLARATIVE_BUILD_DEPRECATED_SINCE(5, 95)
0144 QmlObject::QmlObject(QQmlEngine *engine, QObject *parent)
0145     : QmlObject(std::shared_ptr<QQmlEngine>(engine), nullptr, parent)
0146 {
0147 }
0148 
0149 QmlObject::QmlObject(QQmlEngine *engine, QQmlContext *rootContext, QObject *parent)
0150     : QmlObject(std::shared_ptr<QQmlEngine>(engine), rootContext, parent)
0151 {
0152 }
0153 
0154 QmlObject::QmlObject(QQmlEngine *engine, QQmlContext *rootContext, QmlObject *obj, QObject *parent)
0155     : QmlObject(std::shared_ptr<QQmlEngine>(engine), rootContext, parent)
0156 {
0157     Q_UNUSED(obj);
0158 }
0159 #endif
0160 
0161 QmlObject::QmlObject(std::shared_ptr<QQmlEngine> engine, QQmlContext *rootContext, QObject *parent)
0162     : QObject(parent)
0163     , d(new QmlObjectPrivate(this))
0164 {
0165     if (engine) {
0166         d->engine = engine;
0167     } else {
0168         d->engine = std::make_shared<QQmlEngine>();
0169     }
0170 
0171 #if KDECLARATIVE_BUILD_DEPRECATED_SINCE(5, 98)
0172     if (d->engine.use_count() <= 2) {
0173         KDeclarative::setupEngine(d->engine.get());
0174     }
0175 #endif
0176 
0177     if (rootContext) {
0178         d->rootContext = rootContext;
0179     } else {
0180         d->rootContext = d->engine->rootContext();
0181     }
0182 
0183     d->context = new KLocalizedContext(d->rootContext);
0184     d->rootContext->setContextObject(d->context);
0185 }
0186 
0187 QmlObject::~QmlObject()
0188 {
0189     if (d->engine.use_count() == 1) {
0190         // QQmlEngine does not take ownership of the QNAM factory so we need to
0191         // make sure to clean it, but only if we are the last user of the engine
0192         // otherwise we risk resetting the factory on an engine that is still in
0193         // use.
0194         auto factory = d->engine->networkAccessManagerFactory();
0195         d->engine->setNetworkAccessManagerFactory(nullptr);
0196         delete factory;
0197     }
0198 
0199     delete d;
0200 }
0201 
0202 void QmlObject::setTranslationDomain(const QString &translationDomain)
0203 {
0204     d->context->setTranslationDomain(translationDomain);
0205 }
0206 
0207 QString QmlObject::translationDomain() const
0208 {
0209     return d->context->translationDomain();
0210 }
0211 
0212 void QmlObject::setSource(const QUrl &source)
0213 {
0214     d->source = source;
0215     d->execute(source);
0216 }
0217 
0218 QUrl QmlObject::source() const
0219 {
0220     return d->source;
0221 }
0222 
0223 #if KDECLARATIVE_BUILD_DEPRECATED_SINCE(5, 98)
0224 void QmlObject::loadPackage(const QString &packageName)
0225 {
0226     d->package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/GenericQML"));
0227     d->package.setPath(packageName);
0228     setSource(QUrl::fromLocalFile(d->package.filePath("mainscript")));
0229 }
0230 
0231 void QmlObject::setPackage(const KPackage::Package &package)
0232 {
0233     d->package = package;
0234     setSource(QUrl::fromLocalFile(package.filePath("mainscript")));
0235 }
0236 
0237 KPackage::Package QmlObject::package() const
0238 {
0239     return d->package;
0240 }
0241 #endif
0242 
0243 void QmlObject::setInitializationDelayed(const bool delay)
0244 {
0245     d->delay = delay;
0246 }
0247 
0248 bool QmlObject::isInitializationDelayed() const
0249 {
0250     return d->delay;
0251 }
0252 
0253 QQmlEngine *QmlObject::engine()
0254 {
0255     return d->engine.get();
0256 }
0257 
0258 QObject *QmlObject::rootObject() const
0259 {
0260     if (d->incubator.status() == QQmlIncubator::Loading) {
0261         qWarning() << "Trying to use rootObject before initialization is completed, whilst using setInitializationDelayed. Forcing completion";
0262         d->incubator.forceCompletion();
0263     }
0264     return d->incubator.object();
0265 }
0266 
0267 QQmlComponent *QmlObject::mainComponent() const
0268 {
0269     return d->component;
0270 }
0271 
0272 QQmlContext *QmlObject::rootContext() const
0273 {
0274     return d->rootContext;
0275 }
0276 
0277 QQmlComponent::Status QmlObject::status() const
0278 {
0279     if (!d->engine) {
0280         return QQmlComponent::Error;
0281     }
0282 
0283     if (!d->component) {
0284         return QQmlComponent::Null;
0285     }
0286 
0287     return QQmlComponent::Status(d->component->status());
0288 }
0289 
0290 void QmlObjectPrivate::checkInitializationCompleted()
0291 {
0292     if (!incubator.isReady() && incubator.status() != QQmlIncubator::Error) {
0293         QTimer::singleShot(0, q, SLOT(checkInitializationCompleted()));
0294         return;
0295     }
0296 
0297     if (!incubator.object()) {
0298         errorPrint(component);
0299     }
0300 
0301     Q_EMIT q->finished();
0302 }
0303 
0304 void QmlObject::completeInitialization(const QVariantHash &initialProperties)
0305 {
0306     d->executionEndTimer->stop();
0307     if (d->incubator.object()) {
0308         return;
0309     }
0310 
0311     if (!d->component) {
0312         qWarning() << "No component for" << source();
0313         return;
0314     }
0315 
0316     if (d->component->status() != QQmlComponent::Ready || d->component->isError()) {
0317         d->errorPrint(d->component);
0318         return;
0319     }
0320 
0321     d->incubator.m_initialProperties = initialProperties;
0322     d->component->create(d->incubator, d->rootContext);
0323 
0324     if (d->delay) {
0325         d->checkInitializationCompleted();
0326     } else {
0327         d->incubator.forceCompletion();
0328 
0329         if (!d->incubator.object()) {
0330             d->errorPrint(d->component);
0331         }
0332         Q_EMIT finished();
0333     }
0334 }
0335 
0336 QObject *QmlObject::createObjectFromSource(const QUrl &source, QQmlContext *context, const QVariantHash &initialProperties)
0337 {
0338     QQmlComponent *component = new QQmlComponent(d->engine.get(), this);
0339     component->loadUrl(source);
0340 
0341     return createObjectFromComponent(component, context, initialProperties);
0342 }
0343 
0344 QObject *QmlObject::createObjectFromComponent(QQmlComponent *component, QQmlContext *context, const QVariantHash &initialProperties)
0345 {
0346     QmlObjectIncubator incubator;
0347     incubator.m_initialProperties = initialProperties;
0348     component->create(incubator, context ? context : d->rootContext);
0349     incubator.forceCompletion();
0350 
0351     QObject *object = incubator.object();
0352 
0353     if (!component->isError() && object) {
0354         // memory management
0355         component->setParent(object);
0356         // reparent to root object if wasn't specified otherwise by initialProperties
0357         if (!initialProperties.contains(QLatin1String("parent"))) {
0358             if (qobject_cast<QQuickItem *>(rootObject())) {
0359                 object->setProperty("parent", QVariant::fromValue(rootObject()));
0360             } else {
0361                 object->setParent(rootObject());
0362             }
0363         }
0364 
0365         return object;
0366 
0367     } else {
0368         d->errorPrint(component);
0369         delete object;
0370         return nullptr;
0371     }
0372 }
0373 
0374 }
0375 
0376 #include "moc_qmlobject.cpp"