File indexing completed on 2024-05-05 07:53:52
0001 /* 0002 SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org> 0003 SPDX-FileCopyrightText: 2023 Alexander Lohnau <alexander.lohnau@gmx.de> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only 0006 */ 0007 0008 #include "kcmoduleqml_p.h" 0009 0010 #include <QQuickItem> 0011 #include <QQuickWidget> 0012 #include <QQuickWindow> 0013 #include <QVBoxLayout> 0014 0015 #include <KAboutData> 0016 #include <KLocalizedContext> 0017 #include <KPageWidget> 0018 #include <QQmlEngine> 0019 0020 #include "qml/kquickconfigmodule.h" 0021 0022 #include <kcmutils_debug.h> 0023 0024 class QmlConfigModuleWidget; 0025 class KCModuleQmlPrivate 0026 { 0027 public: 0028 KCModuleQmlPrivate(KQuickConfigModule *cm, KCModuleQml *qq) 0029 : q(qq) 0030 , configModule(std::move(cm)) 0031 { 0032 } 0033 0034 ~KCModuleQmlPrivate() 0035 { 0036 } 0037 0038 void syncCurrentIndex() 0039 { 0040 if (!configModule || !pageRow) { 0041 return; 0042 } 0043 0044 configModule->setCurrentIndex(pageRow->property("currentIndex").toInt()); 0045 } 0046 0047 KCModuleQml *q; 0048 QQuickWindow *quickWindow = nullptr; 0049 QQuickWidget *quickWidget = nullptr; 0050 QQuickItem *rootPlaceHolder = nullptr; 0051 QQuickItem *pageRow = nullptr; 0052 KQuickConfigModule *configModule; 0053 QmlConfigModuleWidget *widget = nullptr; 0054 }; 0055 0056 class QmlConfigModuleWidget : public QWidget 0057 { 0058 Q_OBJECT 0059 public: 0060 QmlConfigModuleWidget(KCModuleQml *module, QWidget *parent) 0061 : QWidget(parent) 0062 , m_module(module) 0063 { 0064 setFocusPolicy(Qt::StrongFocus); 0065 } 0066 0067 void focusInEvent(QFocusEvent *event) override 0068 { 0069 if (event->reason() == Qt::TabFocusReason) { 0070 m_module->d->rootPlaceHolder->nextItemInFocusChain(true)->forceActiveFocus(Qt::TabFocusReason); 0071 } else if (event->reason() == Qt::BacktabFocusReason) { 0072 m_module->d->rootPlaceHolder->nextItemInFocusChain(false)->forceActiveFocus(Qt::BacktabFocusReason); 0073 } 0074 } 0075 0076 QSize sizeHint() const override 0077 { 0078 if (!m_module->d->rootPlaceHolder) { 0079 return QSize(); 0080 } 0081 0082 return QSize(m_module->d->rootPlaceHolder->implicitWidth(), m_module->d->rootPlaceHolder->implicitHeight()); 0083 } 0084 0085 bool eventFilter(QObject *watched, QEvent *event) override 0086 { 0087 if (watched == m_module->d->rootPlaceHolder && event->type() == QEvent::FocusIn) { 0088 auto focusEvent = static_cast<QFocusEvent *>(event); 0089 if (focusEvent->reason() == Qt::TabFocusReason) { 0090 QWidget *w = m_module->d->quickWidget->nextInFocusChain(); 0091 while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) { 0092 w = w->nextInFocusChain(); 0093 } 0094 w->setFocus(Qt::TabFocusReason); // allow tab navigation inside the qquickwidget 0095 return true; 0096 } else if (focusEvent->reason() == Qt::BacktabFocusReason) { 0097 QWidget *w = m_module->d->quickWidget->previousInFocusChain(); 0098 while (!w->isEnabled() || !(w->focusPolicy() & Qt::TabFocus)) { 0099 w = w->previousInFocusChain(); 0100 } 0101 w->setFocus(Qt::BacktabFocusReason); 0102 return true; 0103 } 0104 } 0105 return QWidget::eventFilter(watched, event); 0106 } 0107 0108 private: 0109 KCModuleQml *m_module; 0110 }; 0111 0112 KCModuleQml::KCModuleQml(KQuickConfigModule *configModule, QWidget *parent) 0113 : KCModule(parent, configModule->metaData()) 0114 , d(new KCModuleQmlPrivate(configModule, this)) 0115 { 0116 d->widget = new QmlConfigModuleWidget(this, parent); 0117 setButtons(d->configModule->buttons()); 0118 connect(d->configModule, &KQuickConfigModule::buttonsChanged, d->configModule, [this] { 0119 setButtons(d->configModule->buttons()); 0120 }); 0121 0122 setNeedsSave(d->configModule->needsSave()); 0123 connect(d->configModule, &KQuickConfigModule::needsSaveChanged, this, [this] { 0124 setNeedsSave(d->configModule->needsSave()); 0125 }); 0126 0127 setRepresentsDefaults(d->configModule->representsDefaults()); 0128 connect(d->configModule, &KQuickConfigModule::representsDefaultsChanged, this, [this] { 0129 setRepresentsDefaults(d->configModule->representsDefaults()); 0130 }); 0131 0132 setAuthActionName(d->configModule->authActionName()); 0133 connect(d->configModule, &KQuickConfigModule::authActionNameChanged, this, [this] { 0134 setAuthActionName(d->configModule->authActionName()); 0135 }); 0136 0137 connect(this, &KCModule::defaultsIndicatorsVisibleChanged, d->configModule, [this] { 0138 d->configModule->setDefaultsIndicatorsVisible(defaultsIndicatorsVisible()); 0139 }); 0140 0141 connect(this, &KAbstractConfigModule::activationRequested, d->configModule, &KQuickConfigModule::activationRequested); 0142 0143 // Build the UI 0144 QVBoxLayout *layout = new QVBoxLayout(d->widget); 0145 layout->setContentsMargins(0, 0, 0, 0); 0146 0147 d->quickWidget = new QQuickWidget(d->configModule->engine().get(), d->widget); 0148 d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); 0149 d->quickWidget->setFocusPolicy(Qt::StrongFocus); 0150 d->quickWidget->setAttribute(Qt::WA_AlwaysStackOnTop, true); 0151 d->quickWidget->setAttribute(Qt::WA_NoMousePropagation, true); // Workaround for QTBUG-109861 to fix drag everywhere 0152 d->quickWindow = d->quickWidget->quickWindow(); 0153 d->quickWindow->setColor(Qt::transparent); 0154 0155 QQmlComponent *component = new QQmlComponent(d->configModule->engine().get(), this); 0156 // this has activeFocusOnTab to notice when the navigation wraps 0157 // around, so when we need to go outside and inside 0158 // pushPage/popPage are needed as push of StackView can't be directly invoked from c++ 0159 // because its parameters are QQmlV4Function which is not public. 0160 // The managers of onEnter/ReturnPressed are a workaround of 0161 // Qt bug https://bugreports.qt.io/browse/QTBUG-70934 0162 // clang-format off 0163 // TODO: move this in an instantiable component which would be used by the qml-only version as well 0164 component->setData(QByteArrayLiteral(R"( 0165 import QtQuick 0166 import QtQuick.Controls as QQC2 0167 import org.kde.kirigami 2 as Kirigami 0168 import org.kde.kcmutils as KCMUtils 0169 0170 Kirigami.ApplicationItem { 0171 // force it to *never* try to resize itself 0172 width: Window.width 0173 0174 implicitWidth: Math.max(pageStack.implicitWidth, Kirigami.Units.gridUnit * 36) 0175 implicitHeight: Math.max(pageStack.implicitHeight, Kirigami.Units.gridUnit * 20) 0176 0177 activeFocusOnTab: true 0178 0179 property KCMUtils.ConfigModule kcm 0180 0181 QQC2.ToolButton { 0182 id: toolButton 0183 visible: false 0184 icon.name: "go-previous" 0185 } 0186 0187 pageStack.separatorVisible: pageStack.depth > 0 && pageStack.items[0].sidebarMode 0188 pageStack.globalToolBar.preferredHeight: toolButton.implicitHeight + Kirigami.Units.smallSpacing * 2 0189 pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar 0190 pageStack.globalToolBar.showNavigationButtons: pageStack.currentIndex > 0 ? Kirigami.ApplicationHeaderStyle.ShowBackButton : Kirigami.ApplicationHeaderStyle.NoNavigationButtons 0191 0192 pageStack.columnView.columnResizeMode: pageStack.items.length > 0 && (pageStack.items[0].Kirigami.ColumnView.fillWidth || pageStack.items.filter(item => item.visible).length === 1) 0193 ? Kirigami.ColumnView.SingleColumn 0194 : Kirigami.ColumnView.FixedColumns 0195 0196 pageStack.defaultColumnWidth: kcm && kcm.columnWidth > 0 ? kcm.columnWidth : Kirigami.Units.gridUnit * 15 0197 0198 footer: null 0199 Keys.onReturnPressed: event => { 0200 event.accepted = true 0201 } 0202 Keys.onEnterPressed: event => { 0203 event.accepted = true 0204 } 0205 0206 Window.onWindowChanged: { 0207 if (Window.window) { 0208 Window.window.LayoutMirroring.enabled = Qt.binding(() => Qt.application.layoutDirection === Qt.RightToLeft) 0209 Window.window.LayoutMirroring.childrenInherit = true 0210 } 0211 } 0212 } 0213 )"), QUrl(QStringLiteral("kcmutils/kcmmoduleqml.cpp"))); 0214 // clang-format on 0215 0216 d->rootPlaceHolder = qobject_cast<QQuickItem *>(component->create()); 0217 if (!d->rootPlaceHolder) { 0218 qCCritical(KCMUTILS_LOG) << component->errors(); 0219 qFatal("Failed to initialize KCModuleQML"); 0220 } 0221 d->rootPlaceHolder->setProperty("kcm", QVariant::fromValue(d->configModule)); 0222 d->rootPlaceHolder->installEventFilter(d->widget); 0223 d->quickWidget->setContent(QUrl(), component, d->rootPlaceHolder); 0224 0225 d->pageRow = d->rootPlaceHolder->property("pageStack").value<QQuickItem *>(); 0226 if (d->pageRow) { 0227 d->pageRow->setProperty("initialPage", QVariant::fromValue(d->configModule->mainUi())); 0228 0229 for (int i = 0; i < d->configModule->depth() - 1; i++) { 0230 QMetaObject::invokeMethod(d->pageRow, 0231 "push", 0232 Qt::DirectConnection, 0233 Q_ARG(QVariant, QVariant::fromValue(d->configModule->subPage(i))), 0234 Q_ARG(QVariant, QVariant())); 0235 if (d->configModule->mainUi()->property("sidebarMode").toBool()) { 0236 d->pageRow->setProperty("currentIndex", 0); 0237 d->configModule->setCurrentIndex(0); 0238 } 0239 } 0240 0241 connect(d->configModule, &KQuickConfigModule::pagePushed, this, [this](QQuickItem *page) { 0242 QMetaObject::invokeMethod(d->pageRow, "push", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(page)), Q_ARG(QVariant, QVariant())); 0243 }); 0244 connect(d->configModule, &KQuickConfigModule::pageRemoved, this, [this]() { 0245 QMetaObject::invokeMethod(d->pageRow, "pop", Qt::DirectConnection, Q_ARG(QVariant, QVariant())); 0246 }); 0247 connect(d->configModule, &KQuickConfigModule::currentIndexChanged, this, [this]() { 0248 d->pageRow->setProperty("currentIndex", d->configModule->currentIndex()); 0249 }); 0250 // New syntax cannot be used to connect to QML types 0251 connect(d->pageRow, SIGNAL(currentIndexChanged()), this, SLOT(syncCurrentIndex())); 0252 } 0253 0254 layout->addWidget(d->quickWidget); 0255 } 0256 0257 KCModuleQml::~KCModuleQml() = default; 0258 0259 void KCModuleQml::load() 0260 { 0261 KCModule::load(); // calls setNeedsSave(false) 0262 d->configModule->load(); 0263 } 0264 0265 void KCModuleQml::save() 0266 { 0267 d->configModule->save(); 0268 d->configModule->setNeedsSave(false); 0269 } 0270 0271 void KCModuleQml::defaults() 0272 { 0273 d->configModule->defaults(); 0274 } 0275 0276 QWidget *KCModuleQml::widget() 0277 { 0278 return d->widget; 0279 } 0280 0281 #include "kcmoduleqml.moc" 0282 #include "moc_kcmoduleqml_p.cpp"