From 2f64036a4f536eda799145dbe880f8293b684ada Mon Sep 17 00:00:00 2001 From: Devin Lin Date: Tue, 2 Jul 2024 19:54:39 -0400 Subject: [PATCH] quicksettingsplugin: Load quicksettings QML async Resolves https://invent.kde.org/plasma/plasma-mobile/-/issues/379 This loads the quicksettings packages asynchronously. It also adds support for listening to model changes properly (row add/remove/move events) rather than have it reset every time a change is done. --- .../quicksettingsmodel.cpp | 205 +++++++++++++----- .../quicksettingsplugin/quicksettingsmodel.h | 17 +- 2 files changed, 165 insertions(+), 57 deletions(-) diff --git a/components/quicksettingsplugin/quicksettingsmodel.cpp b/components/quicksettingsplugin/quicksettingsmodel.cpp index 235284ca..c228f5ef 100644 --- a/components/quicksettingsplugin/quicksettingsmodel.cpp +++ b/components/quicksettingsplugin/quicksettingsmodel.cpp @@ -1,6 +1,6 @@ /* * SPDX-FileCopyrightText: 2021 Aleix Pol Gonzalez - * SPDX-FileCopyrightText: 2022 Devin Lin + * SPDX-FileCopyrightText: 2022-2024 Devin Lin * * SPDX-License-Identifier: LGPL-2.0-or-later */ @@ -9,19 +9,46 @@ #include -#include -#include -#include -#include #include +#include +#include +#include QuickSettingsModel::QuickSettingsModel(QObject *parent) : QAbstractListModel{parent} , m_savedQuickSettings{new SavedQuickSettings{this}} { - connect(m_savedQuickSettings->enabledQuickSettingsModel(), &SavedQuickSettingsModel::dataUpdated, this, [this]() { + // Listen to events to enabled quicksettings, and update accordingly + connect(m_savedQuickSettings->enabledQuickSettingsModel(), &SavedQuickSettingsModel::modelReset, this, [this]() { loadQuickSettings(); }); + connect(m_savedQuickSettings->enabledQuickSettingsModel(), &SavedQuickSettingsModel::rowsInserted, this, [this](const QModelIndex &, int first, int last) { + for (int i = first; i <= last; ++i) { + KPluginMetaData metaData = m_savedQuickSettings->enabledQuickSettingsModel()->takeRow(i); + loadQuickSetting(metaData, true); + } + }); + connect(m_savedQuickSettings->enabledQuickSettingsModel(), + &SavedQuickSettingsModel::rowsAboutToBeRemoved, + this, + [this](const QModelIndex &, int first, int last) { + for (int i = first; i <= last; ++i) { + KPluginMetaData metaData = m_savedQuickSettings->enabledQuickSettingsModel()->takeRow(i); + auto index = m_quickSettingsMetaData.indexOf(metaData); + removeQuickSetting(index); + } + }); + connect(m_savedQuickSettings->enabledQuickSettingsModel(), + &SavedQuickSettingsModel::rowsMoved, + this, + [this](const QModelIndex &, int sourceStart, int sourceEnd, const QModelIndex &, int) { + for (int i = sourceStart; i <= sourceEnd; ++i) { + KPluginMetaData metaData = m_savedQuickSettings->enabledQuickSettingsModel()->takeRow(i); + auto index = m_quickSettingsMetaData.indexOf(metaData); + removeQuickSetting(index); + loadQuickSetting(metaData, true); + } + }); } void QuickSettingsModel::classBegin() @@ -67,67 +94,137 @@ void QuickSettingsModel::loadQuickSettings() quickSetting->deleteLater(); } m_quickSettings.clear(); + m_quickSettingsMetaData.clear(); - QQmlEngine *engine = qmlEngine(this); - QQmlComponent *c = new QQmlComponent(engine, this); - - // loop through enabled quick settings metadata + // Loop through enabled quick settings and start loading them for (const auto &metaData : m_savedQuickSettings->enabledQuickSettingsModel()->list()) { - // load kpackage - KPackage::Package package = KPackage::PackageLoader::self()->loadPackage("KPackage/GenericQML", QFileInfo(metaData.fileName()).path()); - if (!package.isValid()) { - continue; - } - - // load QML from kpackage - c->loadUrl(package.fileUrl("mainscript"), QQmlComponent::PreferSynchronous); - KLocalizedContext *i18nContext = new KLocalizedContext(engine); - i18nContext->setTranslationDomain(QLatin1String("plasma_") + metaData.pluginId()); - engine->rootContext()->setContextObject(i18nContext); - - auto created = c->create(engine->rootContext()); - auto createdSetting = qobject_cast(created); - - // print errors if there were issues loading - if (!createdSetting) { - qWarning() << "Unable to load quick setting element:" << created; - for (auto error : c->errors()) { - qWarning() << error; - } - delete created; - } else { - if (createdSetting->isAvailable()) { - m_quickSettings.push_back(createdSetting); - } - connect(createdSetting, &QuickSetting::availableChanged, this, &QuickSettingsModel::availabilityChanged); - } + // false - Ensure row insertion signals aren't emitted (since we are resetting the model) + loadQuickSetting(metaData, false); } - delete c; - endResetModel(); Q_EMIT countChanged(); } -void QuickSettingsModel::availabilityChanged() +void QuickSettingsModel::loadQuickSetting(KPluginMetaData metaData, bool emitInsertSignal) { - auto setting = qobject_cast(sender()); + if (!m_loaded) { + return; + } - if (setting->isAvailable()) { - if (!m_quickSettings.contains(setting)) { - auto idx = m_quickSettings.count(); - beginInsertRows({}, idx, idx); - m_quickSettings.append(setting); - endInsertRows(); - } + // Load KPackage + const KPackage::Package package = KPackage::PackageLoader::self()->loadPackage("KPackage/GenericQML", QFileInfo(metaData.fileName()).path()); + if (!package.isValid()) { + return; + } + + // Create translation context + QQmlEngine *engine = qmlEngine(this); + KLocalizedContext *i18nContext = new KLocalizedContext(engine); + i18nContext->setTranslationDomain(QLatin1String("plasma_") + metaData.pluginId()); + engine->rootContext()->setContextObject(i18nContext); + + QQmlComponent *component = new QQmlComponent(engine, this); + + // Load QML from KPackage async + component->loadUrl(package.fileUrl("mainscript"), QQmlComponent::Asynchronous); + + if (component->isLoading()) { + // Listen to load completion + connect(component, &QQmlComponent::statusChanged, this, [this, metaData, component, engine]() { + afterQuickSettingLoad(engine, metaData, component, true); + }); } else { - auto idx = m_quickSettings.indexOf(setting); - if (idx >= 0) { - beginRemoveRows({}, idx, idx); - m_quickSettings.removeAt(idx); - endRemoveRows(); + // Only emit insertion signal if we aren't resetting the model + afterQuickSettingLoad(engine, metaData, component, emitInsertSignal); + } +} + +void QuickSettingsModel::removeQuickSetting(int index) +{ + if (index >= 0) { + beginRemoveRows({}, index, 0); + m_quickSettings.removeAt(index); + m_quickSettingsMetaData.removeAt(index); + endRemoveRows(); + Q_EMIT countChanged(); + } +} + +void QuickSettingsModel::afterQuickSettingLoad(QQmlEngine *engine, KPluginMetaData metaData, QQmlComponent *component, bool emitInsertSignal) +{ + // Create quicksetting component + QObject *object = component->create(engine->rootContext()); + if (!object) { + qWarning() << "Unable to load quick setting element:" << metaData.pluginId(); + component->deleteLater(); + return; + } + + if (component->isError()) { + // Print errors + qWarning() << "Unable to load quick setting element:" << metaData.pluginId(); + for (auto error : component->errors()) { + qWarning() << error; + } + + component->deleteLater(); + } else if (component->isReady()) { + component->deleteLater(); + + auto createdSetting = qobject_cast(object); + + // Connect availability signal to insert/remove quicksetting into model + connect(createdSetting, &QuickSetting::availableChanged, this, [this, metaData, createdSetting]() { + availabilityChanged(metaData, createdSetting); + }); + + // Add quicksetting to model if available + if (createdSetting->isAvailable()) { + insertQuickSettingToModel(metaData, createdSetting, emitInsertSignal); + } + } +} + +void QuickSettingsModel::insertQuickSettingToModel(KPluginMetaData metaData, QuickSetting *quickSetting, bool emitInsertSignal) +{ + // Insert into correct position based on the saved quick settings order + int insertIndex = 0; + auto list = m_savedQuickSettings->enabledQuickSettingsModel()->list(); + for (int i = 0; i < list.size(); ++i) { + if (insertIndex >= m_quickSettingsMetaData.size()) { + break; + } + + if (list[i].pluginId() == m_quickSettingsMetaData[insertIndex].pluginId()) { + if (metaData.pluginId() == list[i].pluginId()) { + break; + } + ++insertIndex; } } + if (emitInsertSignal) { + beginInsertRows({}, insertIndex, 0); + } + + m_quickSettings.insert(insertIndex, quickSetting); + m_quickSettingsMetaData.insert(insertIndex, metaData); + + if (emitInsertSignal) { + endInsertRows(); + } Q_EMIT countChanged(); } + +void QuickSettingsModel::availabilityChanged(KPluginMetaData metaData, QuickSetting *quickSetting) +{ + if (quickSetting->isAvailable()) { + if (!m_quickSettings.contains(quickSetting)) { + insertQuickSettingToModel(metaData, quickSetting, true); + } + } else { + auto idx = m_quickSettings.indexOf(quickSetting); + removeQuickSetting(idx); + } +} diff --git a/components/quicksettingsplugin/quicksettingsmodel.h b/components/quicksettingsplugin/quicksettingsmodel.h index a98ee150..064bb3b0 100644 --- a/components/quicksettingsplugin/quicksettingsmodel.h +++ b/components/quicksettingsplugin/quicksettingsmodel.h @@ -13,6 +13,7 @@ #include "savedquicksettingsmodel.h" #include +#include #include class QuickSettingsModel : public QAbstractListModel, public QQmlParserStatus @@ -38,9 +39,19 @@ Q_SIGNALS: private: void loadQuickSettings(); - void availabilityChanged(); + void availabilityChanged(KPluginMetaData metaData, QuickSetting *quickSetting); - bool m_loaded = false; + void loadQuickSetting(KPluginMetaData metaData, bool emitInsertSignal); + void removeQuickSetting(int index); + + void afterQuickSettingLoad(QQmlEngine *engine, KPluginMetaData metaData, QQmlComponent *component, bool emitInsertSignal); + void insertQuickSettingToModel(KPluginMetaData metaData, QuickSetting *quickSetting, bool emitInsertSignal); + + bool m_loaded{false}; + + // m_quickSettings and m_quickSettingsMetaData indices match to same quick setting QList m_quickSettings; - SavedQuickSettings *m_savedQuickSettings; + QList m_quickSettingsMetaData; + + SavedQuickSettings *m_savedQuickSettings{nullptr}; };