File indexing completed on 2025-03-09 05:02:43

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.h"
0009 #include "appletcontext_p.h"
0010 
0011 #include <KLocalizedContext>
0012 #include <QDebug>
0013 #include <QQmlContext>
0014 #include <QQmlEngine>
0015 #include <QQmlNetworkAccessManagerFactory>
0016 #include <QQuickItem>
0017 #include <QTimer>
0018 
0019 #include <Plasma/Applet>
0020 
0021 #include "debug_p.h"
0022 
0023 namespace PlasmaQuick
0024 {
0025 
0026 class SharedQmlEnginePrivate
0027 {
0028 public:
0029     SharedQmlEnginePrivate(SharedQmlEngine *parent)
0030         : q(parent)
0031         , component(nullptr)
0032         , delay(false)
0033         , m_engine(engine())
0034     {
0035         executionEndTimer = new QTimer(q);
0036         executionEndTimer->setInterval(0);
0037         executionEndTimer->setSingleShot(true);
0038         QObject::connect(executionEndTimer, &QTimer::timeout, q, [this]() {
0039             scheduleExecutionEnd();
0040         });
0041     }
0042 
0043     ~SharedQmlEnginePrivate() = default;
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 
0055     SharedQmlEngine *q;
0056 
0057     QUrl source;
0058 
0059     QPointer<QObject> rootObject;
0060     QQmlComponent *component;
0061     QTimer *executionEndTimer;
0062     KLocalizedContext *context{nullptr};
0063     QQmlContext *rootContext;
0064     bool delay;
0065     std::shared_ptr<QQmlEngine> m_engine;
0066 
0067 private:
0068     static std::shared_ptr<QQmlEngine> engine()
0069     {
0070         if (auto locked = s_engine.lock()) {
0071             return locked;
0072         }
0073         auto createdEngine = std::make_shared<QQmlEngine>();
0074         s_engine = createdEngine;
0075         return createdEngine;
0076     }
0077 
0078     static std::weak_ptr<QQmlEngine> s_engine;
0079 };
0080 
0081 std::weak_ptr<QQmlEngine> SharedQmlEnginePrivate::s_engine = {};
0082 
0083 void SharedQmlEnginePrivate::errorPrint(QQmlComponent *component)
0084 {
0085     QString errorStr = QStringLiteral("Error loading QML file.\n");
0086     if (component->isError()) {
0087         const QList<QQmlError> errors = component->errors();
0088         for (const QQmlError &error : errors) {
0089             errorStr +=
0090                 (error.line() > 0 ? QString(QString::number(error.line()) + QLatin1String(": ")) : QLatin1String("")) + error.description() + QLatin1Char('\n');
0091         }
0092     }
0093     qWarning(LOG_PLASMAQUICK) << component->url().toString() << '\n' << errorStr;
0094 }
0095 
0096 void SharedQmlEnginePrivate::execute(const QUrl &source)
0097 {
0098     if (source.isEmpty()) {
0099         qWarning(LOG_PLASMAQUICK) << "File name empty!";
0100         return;
0101     }
0102 
0103     delete component;
0104     component = new QQmlComponent(m_engine.get(), q);
0105     QObject::connect(component, &QQmlComponent::statusChanged, q, &SharedQmlEngine::statusChanged, Qt::QueuedConnection);
0106 
0107     component->loadUrl(source);
0108     rootObject = component->beginCreate(rootContext);
0109 
0110     if (delay) {
0111         executionEndTimer->start(0);
0112     } else {
0113         scheduleExecutionEnd();
0114     }
0115 }
0116 
0117 void SharedQmlEnginePrivate::scheduleExecutionEnd()
0118 {
0119     if (component->isReady() || component->isError()) {
0120         q->completeInitialization();
0121     } else {
0122         QObject::connect(component, &QQmlComponent::statusChanged, q, [this]() {
0123             q->completeInitialization();
0124         });
0125     }
0126 }
0127 
0128 SharedQmlEngine::SharedQmlEngine(QObject *parent)
0129     : QObject(parent)
0130     , d(new SharedQmlEnginePrivate(this))
0131 {
0132     d->rootContext = new QQmlContext(engine().get());
0133     d->rootContext->setParent(this); // Delete the context when deleting the shared engine
0134 
0135     d->context = new KLocalizedContext(d->rootContext);
0136     d->rootContext->setContextObject(d->context);
0137 }
0138 
0139 SharedQmlEngine::SharedQmlEngine(Plasma::Applet *applet, QObject *parent)
0140     : QObject(parent)
0141     , d(new SharedQmlEnginePrivate(this))
0142 {
0143     d->rootContext = new AppletContext(engine().get(), applet, this);
0144 
0145     d->context = new KLocalizedContext(d->rootContext);
0146     d->rootContext->setContextObject(d->context);
0147 }
0148 
0149 SharedQmlEngine::~SharedQmlEngine()
0150 {
0151     delete d->component;
0152     if (QJSEngine::objectOwnership(d->rootObject) == QJSEngine::CppOwnership) {
0153         delete d->rootObject;
0154     }
0155 }
0156 
0157 void SharedQmlEngine::setTranslationDomain(const QString &translationDomain)
0158 {
0159     d->context->setTranslationDomain(translationDomain);
0160 }
0161 
0162 QString SharedQmlEngine::translationDomain() const
0163 {
0164     return d->context->translationDomain();
0165 }
0166 
0167 void SharedQmlEngine::setSource(const QUrl &source)
0168 {
0169     d->source = source;
0170     d->execute(source);
0171 }
0172 
0173 QUrl SharedQmlEngine::source() const
0174 {
0175     return d->source;
0176 }
0177 
0178 void SharedQmlEngine::setInitializationDelayed(const bool delay)
0179 {
0180     d->delay = delay;
0181 }
0182 
0183 bool SharedQmlEngine::isInitializationDelayed() const
0184 {
0185     return d->delay;
0186 }
0187 
0188 std::shared_ptr<QQmlEngine> SharedQmlEngine::engine()
0189 {
0190     return d->m_engine;
0191 }
0192 
0193 QObject *SharedQmlEngine::rootObject() const
0194 {
0195     return d->rootObject;
0196 }
0197 
0198 QQmlComponent *SharedQmlEngine::mainComponent() const
0199 {
0200     return d->component;
0201 }
0202 
0203 QQmlContext *SharedQmlEngine::rootContext() const
0204 {
0205     return d->rootContext;
0206 }
0207 
0208 QQmlComponent::Status SharedQmlEngine::status() const
0209 {
0210     if (!d->m_engine) {
0211         return QQmlComponent::Error;
0212     }
0213 
0214     if (!d->component) {
0215         return QQmlComponent::Null;
0216     }
0217 
0218     return QQmlComponent::Status(d->component->status());
0219 }
0220 
0221 void SharedQmlEngine::completeInitialization(const QVariantHash &initialProperties)
0222 {
0223     d->executionEndTimer->stop();
0224 
0225     if (!d->component) {
0226         qWarning(LOG_PLASMAQUICK) << "No component for" << source();
0227         return;
0228     }
0229 
0230     if (d->component->status() != QQmlComponent::Ready || d->component->isError()) {
0231         d->errorPrint(d->component);
0232         return;
0233     }
0234 
0235     for (auto it = initialProperties.constBegin(); it != initialProperties.constEnd(); ++it) {
0236         d->rootObject->setProperty(it.key().toUtf8().data(), it.value());
0237     }
0238 
0239     d->component->completeCreate();
0240     Q_EMIT finished();
0241 }
0242 
0243 QObject *SharedQmlEngine::createObjectFromSource(const QUrl &source, QQmlContext *context, const QVariantHash &initialProperties)
0244 {
0245     QQmlComponent *component = new QQmlComponent(d->m_engine.get(), this);
0246     component->loadUrl(source);
0247 
0248     return createObjectFromComponent(component, context, initialProperties);
0249 }
0250 
0251 QObject *SharedQmlEngine::createObjectFromComponent(QQmlComponent *component, QQmlContext *context, const QVariantHash &initialProperties)
0252 {
0253     QObject *object = component->beginCreate(context ? context : d->rootContext);
0254 
0255     for (auto it = initialProperties.constBegin(); it != initialProperties.constEnd(); ++it) {
0256         object->setProperty(it.key().toUtf8().data(), it.value());
0257     }
0258     component->completeCreate();
0259 
0260     if (!component->isError() && object) {
0261         // memory management
0262         component->setParent(object);
0263         // reparent to root object if wasn't specified otherwise by initialProperties
0264         if (!initialProperties.contains(QLatin1String("parent"))) {
0265             if (qobject_cast<QQuickItem *>(rootObject())) {
0266                 object->setProperty("parent", QVariant::fromValue(rootObject()));
0267             } else {
0268                 object->setParent(rootObject());
0269             }
0270         }
0271 
0272         return object;
0273 
0274     } else {
0275         d->errorPrint(component);
0276         delete object;
0277         return nullptr;
0278     }
0279 }
0280 }
0281 
0282 #include "moc_sharedqmlengine.cpp"