File indexing completed on 2024-04-21 14:53:51

0001 /*
0002     This file is part of the KDE Project
0003     SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "kcmoduleqml_p.h"
0009 
0010 #include <kcmutils_debug.h>
0011 
0012 #include <QQuickItem>
0013 #include <QQuickWidget>
0014 #include <QQuickWindow>
0015 #include <QVBoxLayout>
0016 
0017 #include <KAboutData>
0018 #include <KPageWidget>
0019 #include <kdeclarative/kdeclarative.h>
0020 #include <kdeclarative/kdeclarative_export.h>
0021 #include <kdeclarative/qmlobjectsharedengine.h>
0022 #include <kquickaddons/configmodule.h>
0023 
0024 class KCModuleQmlPrivate
0025 {
0026 public:
0027     KCModuleQmlPrivate(std::unique_ptr<KQuickAddons::ConfigModule> cm, KCModuleQml *qq)
0028         : q(qq)
0029         , configModule(std::move(cm))
0030     {
0031     }
0032 
0033     ~KCModuleQmlPrivate()
0034     {
0035     }
0036 
0037     void syncCurrentIndex()
0038     {
0039         if (!configModule || !pageRow) {
0040             return;
0041         }
0042 
0043         configModule->setCurrentIndex(pageRow->property("currentIndex").toInt());
0044     }
0045 
0046     KCModuleQml *q;
0047     QQuickWindow *quickWindow = nullptr;
0048     QQuickWidget *quickWidget = nullptr;
0049     QQuickItem *rootPlaceHolder = nullptr;
0050     QQuickItem *pageRow = nullptr;
0051     std::unique_ptr<KQuickAddons::ConfigModule> configModule;
0052     KDeclarative::QmlObjectSharedEngine *qmlObject = nullptr;
0053 };
0054 
0055 KCModuleQml::KCModuleQml(std::unique_ptr<KQuickAddons::ConfigModule> configModule, QWidget *parent, const QVariantList &args)
0056     : KCModule(parent, args)
0057     , d(new KCModuleQmlPrivate(std::move(configModule), this))
0058 {
0059     connect(d->configModule.get(), &KQuickAddons::ConfigModule::quickHelpChanged, this, &KCModuleQml::quickHelpChanged);
0060     // HACK:Here is important those two enums keep having the exact same values
0061     // but the kdeclarative one can't use the KCModule's enum
0062     setButtons((KCModule::Buttons)(int)d->configModule->buttons());
0063     connect(d->configModule.get(), &KQuickAddons::ConfigModule::buttonsChanged, this, [=] {
0064         setButtons((KCModule::Buttons)(int)d->configModule->buttons());
0065     });
0066 
0067     if (d->configModule->needsSave()) {
0068         Q_EMIT changed(true);
0069     }
0070     connect(d->configModule.get(), &KQuickAddons::ConfigModule::needsSaveChanged, this, [=] {
0071         Q_EMIT changed(d->configModule->needsSave());
0072     });
0073     connect(d->configModule.get(), &KQuickAddons::ConfigModule::representsDefaultsChanged, this, [=] {
0074         Q_EMIT defaulted(d->configModule->representsDefaults());
0075     });
0076 
0077     setNeedsAuthorization(d->configModule->needsAuthorization());
0078     connect(d->configModule.get(), &KQuickAddons::ConfigModule::needsAuthorizationChanged, this, [=] {
0079         setNeedsAuthorization(d->configModule->needsAuthorization());
0080     });
0081 
0082     setRootOnlyMessage(d->configModule->rootOnlyMessage());
0083     setUseRootOnlyMessage(d->configModule->useRootOnlyMessage());
0084     connect(d->configModule.get(), &KQuickAddons::ConfigModule::rootOnlyMessageChanged, this, [=] {
0085         setRootOnlyMessage(d->configModule->rootOnlyMessage());
0086     });
0087     connect(d->configModule.get(), &KQuickAddons::ConfigModule::useRootOnlyMessageChanged, this, [=] {
0088         setUseRootOnlyMessage(d->configModule->useRootOnlyMessage());
0089     });
0090 
0091 #if KCONFIGWIDGETS_WITH_KAUTH
0092     if (!d->configModule->authActionName().isEmpty()) {
0093         setAuthAction(KAuth::Action(d->configModule->authActionName()));
0094     }
0095     connect(d->configModule.get(), &KQuickAddons::ConfigModule::authActionNameChanged, this, [=] {
0096         setAuthAction(d->configModule->authActionName());
0097     });
0098 #endif
0099 
0100     connect(this, &KCModule::defaultsIndicatorsVisibleChanged, d->configModule.get(), &KQuickAddons::ConfigModule::setDefaultsIndicatorsVisible);
0101 
0102 #if QUICKADDONS_BUILD_DEPRECATED_SINCE(5, 88)
0103     // KCModule takes ownership of the kabout data so we need to force a copy
0104     QT_WARNING_PUSH
0105     QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
0106     QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
0107     setAboutData(new KAboutData(*d->configModule->aboutData()));
0108     QT_WARNING_POP
0109 #endif
0110     setFocusPolicy(Qt::StrongFocus);
0111 
0112     // Build the UI
0113     QVBoxLayout *layout = new QVBoxLayout(this);
0114     layout->setContentsMargins(0, 0, 0, 0);
0115 
0116     d->qmlObject = new KDeclarative::QmlObjectSharedEngine(this);
0117     d->quickWidget = new QQuickWidget(d->qmlObject->engine(), this);
0118     d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
0119     d->quickWidget->setFocusPolicy(Qt::StrongFocus);
0120     d->quickWidget->setAttribute(Qt::WA_AlwaysStackOnTop, true);
0121     d->quickWindow = d->quickWidget->quickWindow();
0122     d->quickWindow->setColor(Qt::transparent);
0123 
0124     QQmlComponent *component = new QQmlComponent(d->qmlObject->engine(), this);
0125     // This has activeFocusOnTab to notice when the navigation wraps
0126     // around, so when we need to go outside and inside
0127     // pushPage/popPage are needed as push of StackView can't be directly invoked from c++
0128     // because its parameters are QQmlV4Function which is not public.
0129     // The managers of onEnter/ReturnPressed are a workaround of
0130     // Qt bug https://bugreports.qt.io/browse/QTBUG-70934
0131     // clang-format off
0132     component->setData(QByteArrayLiteral(R"(
0133 import QtQuick 2.3
0134 import QtQuick.Window 2.2
0135 import QtQuick.Controls 2.2
0136 import org.kde.kirigami 2.14 as Kirigami
0137 
0138 Kirigami.ApplicationItem {
0139     //force it to *never* try to resize itself
0140     width: Window.width
0141 
0142     implicitWidth: pageStack.implicitWidth
0143     implicitHeight: pageStack.implicitHeight
0144 
0145     activeFocusOnTab: true
0146     controlsVisible: false
0147 
0148     property QtObject kcm
0149 
0150     ToolButton {
0151         id:toolButton
0152         visible: false
0153         icon.name: "go-previous"
0154     }
0155 
0156     pageStack.separatorVisible: false
0157     pageStack.globalToolBar.preferredHeight: toolButton.implicitHeight + Kirigami.Units.smallSpacing * 2
0158     pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
0159     pageStack.globalToolBar.showNavigationButtons: pageStack.currentIndex > 0 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : Kirigami.ApplicationHeaderStyle.NoNavigationButtons
0160 
0161     pageStack.columnView.columnResizeMode: pageStack.items.length > 0 && pageStack.items[0].Kirigami.ColumnView.fillWidth
0162         ? Kirigami.ColumnView.SingleColumn
0163         : Kirigami.ColumnView.FixedColumns
0164 
0165     pageStack.defaultColumnWidth: kcm && kcm.columnWidth > 0 ? kcm.columnWidth : Kirigami.Units.gridUnit * 20
0166 
0167     Keys.onReturnPressed: {
0168         event.accepted = true
0169     }
0170     Keys.onEnterPressed: {
0171         event.accepted = true
0172     }
0173 
0174     Window.onWindowChanged: {
0175         if (Window.window) {
0176             Window.window.LayoutMirroring.enabled = Qt.binding(() => Qt.application.layoutDirection === Qt.RightToLeft)
0177             Window.window.LayoutMirroring.childrenInherit = true
0178         }
0179     }
0180 }
0181     )"), QUrl(QStringLiteral("kcmutils/kcmmoduleqml.cpp")));
0182     // clang-format on
0183 
0184     d->rootPlaceHolder = qobject_cast<QQuickItem *>(component->create());
0185     if (!d->rootPlaceHolder) {
0186         qCCritical(KCMUTILS_LOG) << component->errors();
0187         qFatal("Failed to initialize KCModuleQML");
0188     }
0189     d->rootPlaceHolder->setProperty("kcm", QVariant::fromValue(d->configModule.get()));
0190     d->rootPlaceHolder->installEventFilter(this);
0191     d->quickWidget->setContent(QUrl(), component, d->rootPlaceHolder);
0192 
0193     d->pageRow = d->rootPlaceHolder->property("pageStack").value<QQuickItem *>();
0194     if (d->pageRow) {
0195         d->pageRow->setProperty("initialPage", QVariant::fromValue(d->configModule->mainUi()));
0196 
0197         for (int i = 0; i < d->configModule->depth() - 1; i++) {
0198             QMetaObject::invokeMethod(d->pageRow,
0199                                       "push",
0200                                       Qt::DirectConnection,
0201                                       Q_ARG(QVariant, QVariant::fromValue(d->configModule->subPage(i))),
0202                                       Q_ARG(QVariant, QVariant()));
0203         }
0204 
0205         connect(d->configModule.get(), &KQuickAddons::ConfigModule::pagePushed, this, [this](QQuickItem *page) {
0206             QMetaObject::invokeMethod(d->pageRow, "push", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(page)), Q_ARG(QVariant, QVariant()));
0207         });
0208         connect(d->configModule.get(), &KQuickAddons::ConfigModule::pageRemoved, this, [this]() {
0209             QMetaObject::invokeMethod(d->pageRow, "pop", Qt::DirectConnection, Q_ARG(QVariant, QVariant()));
0210         });
0211         connect(d->configModule.get(), &KQuickAddons::ConfigModule::currentIndexChanged, this, [this]() {
0212             d->pageRow->setProperty("currentIndex", d->configModule->currentIndex());
0213         });
0214         connect(d->configModule.get(),
0215                 &KQuickAddons::ConfigModule::passiveNotificationRequested,
0216                 this,
0217                 [this](const QString &message, const QVariant &timeout, const QString &actionText, const QJSValue &callBack) {
0218                     d->rootPlaceHolder->metaObject()->invokeMethod(d->rootPlaceHolder,
0219                                                                    "showPassiveNotification",
0220                                                                    Q_ARG(QVariant, message),
0221                                                                    Q_ARG(QVariant, timeout),
0222                                                                    Q_ARG(QVariant, actionText),
0223                                                                    Q_ARG(QVariant, QVariant::fromValue(callBack)));
0224                 });
0225         // New syntax cannot be used to connect to QML types
0226         connect(d->pageRow, SIGNAL(currentIndexChanged()), this, SLOT(syncCurrentIndex()));
0227     }
0228 
0229     layout->addWidget(d->quickWidget);
0230 }
0231 
0232 KCModuleQml::~KCModuleQml()
0233 {
0234     delete d;
0235 }
0236 
0237 bool KCModuleQml::eventFilter(QObject *watched, QEvent *event)
0238 {
0239     if (watched == d->rootPlaceHolder && event->type() == QEvent::FocusIn) {
0240         auto focusEvent = static_cast<QFocusEvent *>(event);
0241         if (focusEvent->reason() == Qt::TabFocusReason) {
0242             QWidget *w = d->quickWidget->nextInFocusChain();
0243             while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
0244                 w = w->nextInFocusChain();
0245             }
0246             w->setFocus(Qt::TabFocusReason); // allow tab navigation inside the qquickwidget
0247             return true;
0248         } else if (focusEvent->reason() == Qt::BacktabFocusReason) {
0249             QWidget *w = d->quickWidget->previousInFocusChain();
0250             while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) {
0251                 w = w->previousInFocusChain();
0252             }
0253             w->setFocus(Qt::BacktabFocusReason);
0254             return true;
0255         }
0256     }
0257     return KCModule::eventFilter(watched, event);
0258 }
0259 
0260 void KCModuleQml::focusInEvent(QFocusEvent *event)
0261 {
0262     Q_UNUSED(event)
0263 
0264     if (event->reason() == Qt::TabFocusReason) {
0265         d->rootPlaceHolder->nextItemInFocusChain(true)->forceActiveFocus(Qt::TabFocusReason);
0266     } else if (event->reason() == Qt::BacktabFocusReason) {
0267         d->rootPlaceHolder->nextItemInFocusChain(false)->forceActiveFocus(Qt::BacktabFocusReason);
0268     }
0269 }
0270 
0271 QSize KCModuleQml::sizeHint() const
0272 {
0273     if (!d->rootPlaceHolder) {
0274         return QSize();
0275     }
0276 
0277     return QSize(d->rootPlaceHolder->implicitWidth(), d->rootPlaceHolder->implicitHeight());
0278 }
0279 
0280 QString KCModuleQml::quickHelp() const
0281 {
0282     return d->configModule->quickHelp();
0283 }
0284 
0285 #if KCONFIGWIDGETS_BUILD_DEPRECATED_SINCE(5, 90)
0286 const KAboutData *KCModuleQml::aboutData() const
0287 {
0288 #if QUICKADDONS_BUILD_DEPRECATED_SINCE(5, 88)
0289     QT_WARNING_PUSH
0290     QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
0291     QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
0292     return d->configModule->aboutData();
0293     QT_WARNING_POP
0294 #endif
0295 }
0296 #endif
0297 
0298 void KCModuleQml::load()
0299 {
0300     d->configModule->load();
0301     Q_EMIT defaulted(d->configModule->representsDefaults());
0302 }
0303 
0304 void KCModuleQml::save()
0305 {
0306     d->configModule->save();
0307     d->configModule->setNeedsSave(false);
0308 }
0309 
0310 void KCModuleQml::defaults()
0311 {
0312     d->configModule->defaults();
0313 }
0314 
0315 #include "moc_kcmoduleqml_p.cpp"