File indexing completed on 2024-05-12 03:54:10

0001 /*
0002     SPDX-FileCopyrightText: 2013 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "sharedqmlengine_p.h"
0009 
0010 #include <KLocalizedContext>
0011 #include <QDebug>
0012 #include <QQmlContext>
0013 #include <QQmlEngine>
0014 #include <QQmlIncubator>
0015 #include <QQmlNetworkAccessManagerFactory>
0016 #include <QQuickItem>
0017 #include <QResource>
0018 #include <QTimer>
0019 
0020 #include "kcmutils_debug.h"
0021 
0022 using namespace Qt::StringLiterals;
0023 
0024 class SharedQmlEnginePrivate
0025 {
0026 public:
0027     SharedQmlEnginePrivate(const std::shared_ptr<QQmlEngine> &engine, SharedQmlEngine *parent)
0028         : q(parent)
0029         , component(nullptr)
0030         , delay(false)
0031         , m_engine(engine)
0032     {
0033         executionEndTimer.setInterval(0);
0034         executionEndTimer.setSingleShot(true);
0035         QObject::connect(&executionEndTimer, &QTimer::timeout, q, [this]() {
0036             scheduleExecutionEnd();
0037         });
0038     }
0039 
0040     ~SharedQmlEnginePrivate()
0041     {
0042         delete incubator.object();
0043     }
0044 
0045     void errorPrint(QQmlComponent *component);
0046     void execute(const QUrl &source);
0047     void scheduleExecutionEnd();
0048     void minimumWidthChanged();
0049     void minimumHeightChanged();
0050     void maximumWidthChanged();
0051     void maximumHeightChanged();
0052     void preferredWidthChanged();
0053     void preferredHeightChanged();
0054     void checkInitializationCompleted();
0055 
0056     SharedQmlEngine *q;
0057 
0058     QUrl source;
0059 
0060     QQmlIncubator incubator;
0061     QQmlComponent *component;
0062     QTimer executionEndTimer;
0063     KLocalizedContext *context{nullptr};
0064     QQmlContext *rootContext;
0065     bool delay;
0066     std::shared_ptr<QQmlEngine> m_engine;
0067 };
0068 
0069 void SharedQmlEnginePrivate::errorPrint(QQmlComponent *component)
0070 {
0071     if (component->isError()) {
0072         qCWarning(KCMUTILS_LOG).noquote() << "Error loading QML file" << component->url().toString();
0073         const auto errors = component->errors();
0074         for (const auto &error : errors) {
0075             constexpr const QLatin1String indent("    ");
0076             qCWarning(KCMUTILS_LOG).noquote().nospace() << indent << error;
0077         }
0078     }
0079 }
0080 
0081 void SharedQmlEnginePrivate::execute(const QUrl &source)
0082 {
0083     Q_ASSERT(!source.isEmpty());
0084     delete component;
0085     component = new QQmlComponent(m_engine.get(), q);
0086     QObject::connect(component, &QQmlComponent::statusChanged, q, &SharedQmlEngine::statusChanged, Qt::QueuedConnection);
0087     delete incubator.object();
0088 
0089     m_engine->addImportPath(QStringLiteral("qrc:/"));
0090     component->loadUrl(source);
0091 
0092     if (delay) {
0093         executionEndTimer.start(0);
0094     } else {
0095         scheduleExecutionEnd();
0096     }
0097 }
0098 
0099 void SharedQmlEnginePrivate::scheduleExecutionEnd()
0100 {
0101     if (component->isReady() || component->isError()) {
0102         q->completeInitialization();
0103     } else {
0104         QObject::connect(component, &QQmlComponent::statusChanged, q, [this]() {
0105             q->completeInitialization();
0106         });
0107     }
0108 }
0109 
0110 SharedQmlEngine::SharedQmlEngine(const std::shared_ptr<QQmlEngine> &engine, QObject *parent)
0111     : QObject(parent)
0112     , d(new SharedQmlEnginePrivate(engine, this))
0113 {
0114     d->rootContext = new QQmlContext(engine.get());
0115     d->rootContext->setParent(this); // Delete the context when deleting the shared engine
0116 
0117     d->context = new KLocalizedContext(d->rootContext);
0118     d->rootContext->setContextObject(d->context);
0119 }
0120 
0121 SharedQmlEngine::~SharedQmlEngine() = default;
0122 
0123 void SharedQmlEngine::setTranslationDomain(const QString &translationDomain)
0124 {
0125     d->context->setTranslationDomain(translationDomain);
0126 }
0127 
0128 QString SharedQmlEngine::translationDomain() const
0129 {
0130     return d->context->translationDomain();
0131 }
0132 
0133 void SharedQmlEngine::setSource(const QUrl &source)
0134 {
0135     d->source = source;
0136     d->execute(source);
0137 }
0138 
0139 QUrl SharedQmlEngine::source() const
0140 {
0141     return d->source;
0142 }
0143 
0144 void SharedQmlEngine::setInitializationDelayed(const bool delay)
0145 {
0146     d->delay = delay;
0147 }
0148 
0149 bool SharedQmlEngine::isInitializationDelayed() const
0150 {
0151     return d->delay;
0152 }
0153 
0154 std::shared_ptr<QQmlEngine> SharedQmlEngine::engine()
0155 {
0156     return d->m_engine;
0157 }
0158 
0159 QObject *SharedQmlEngine::rootObject() const
0160 {
0161     if (d->incubator.status() == QQmlIncubator::Loading) {
0162         qCWarning(KCMUTILS_LOG) << "Trying to use rootObject before initialization is completed, whilst using setInitializationDelayed. Forcing completion";
0163         d->incubator.forceCompletion();
0164     }
0165     return d->incubator.object();
0166 }
0167 
0168 QQmlComponent *SharedQmlEngine::mainComponent() const
0169 {
0170     return d->component;
0171 }
0172 
0173 QQmlContext *SharedQmlEngine::rootContext() const
0174 {
0175     return d->rootContext;
0176 }
0177 
0178 QQmlComponent::Status SharedQmlEngine::status() const
0179 {
0180     if (!d->m_engine) {
0181         return QQmlComponent::Error;
0182     }
0183 
0184     if (!d->component) {
0185         return QQmlComponent::Null;
0186     }
0187 
0188     return d->component->status();
0189 }
0190 
0191 void SharedQmlEnginePrivate::checkInitializationCompleted()
0192 {
0193     if (!incubator.isReady() && incubator.status() != QQmlIncubator::Error) {
0194         QTimer::singleShot(0, q, [this]() {
0195             checkInitializationCompleted();
0196         });
0197         return;
0198     }
0199 
0200     if (!incubator.object()) {
0201         errorPrint(component);
0202     }
0203 
0204     Q_EMIT q->finished();
0205 }
0206 
0207 void SharedQmlEngine::completeInitialization(const QVariantMap &initialProperties)
0208 {
0209     d->executionEndTimer.stop();
0210     if (d->incubator.object()) {
0211         return;
0212     }
0213 
0214     if (!d->component) {
0215         qCWarning(KCMUTILS_LOG) << "No component for" << source();
0216         return;
0217     }
0218 
0219     if (d->component->status() != QQmlComponent::Ready || d->component->isError()) {
0220         d->errorPrint(d->component);
0221         return;
0222     }
0223 
0224     d->incubator.setInitialProperties(initialProperties);
0225     d->component->create(d->incubator, d->rootContext);
0226 
0227     if (d->delay) {
0228         d->checkInitializationCompleted();
0229     } else {
0230         d->incubator.forceCompletion();
0231 
0232         if (!d->incubator.object()) {
0233             d->errorPrint(d->component);
0234         }
0235         Q_EMIT finished();
0236     }
0237 }
0238 
0239 QObject *SharedQmlEngine::createObjectFromSource(const QUrl &source, QQmlContext *context, const QVariantMap &initialProperties)
0240 {
0241     QQmlComponent *component = new QQmlComponent(d->m_engine.get(), this);
0242     component->loadUrl(source);
0243 
0244     return createObjectFromComponent(component, context, initialProperties);
0245 }
0246 
0247 QObject *SharedQmlEngine::createObjectFromComponent(QQmlComponent *component, QQmlContext *context, const QVariantMap &initialProperties)
0248 {
0249     QObject *object = component->createWithInitialProperties(initialProperties, context ? context : d->rootContext);
0250 
0251     if (!component->isError() && object) {
0252         // memory management
0253         component->setParent(object);
0254         // reparent to root object if wasn't specified otherwise by initialProperties
0255         if (!initialProperties.contains(QLatin1String("parent"))) {
0256             const auto root = rootObject();
0257             if (root && root->isQuickItemType()) {
0258                 object->setProperty("parent", QVariant::fromValue(root));
0259             } else {
0260                 object->setParent(root);
0261             }
0262         }
0263 
0264         return object;
0265 
0266     } else {
0267         d->errorPrint(component);
0268         delete object;
0269         return nullptr;
0270     }
0271 }
0272 
0273 #include "moc_sharedqmlengine_p.cpp"