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"