diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a17f4a..dd5cbd6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,111 +1,112 @@ ########### kcmutils ############### set(kcmutils_LIB_SRCS kcmoduleinfo.cpp kcmoduleloader.cpp kcmoduleqml.cpp kcmultidialog.cpp kcmoduleproxy.cpp kpluginselector.cpp kcmodulecontainer.cpp ksettingswidgetadaptor.cpp ksettings/dispatcher.cpp ksettings/dialog.cpp ksettings/pluginpage.cpp ksettings/componentsdialog.cpp ) add_library(KF5KCMUtils ${kcmutils_LIB_SRCS}) generate_export_header(KF5KCMUtils BASE_NAME KCMUtils) target_include_directories(KF5KCMUtils INTERFACE "$") target_link_libraries(KF5KCMUtils PUBLIC Qt5::Widgets KF5::ConfigWidgets # KCModule KF5::Service # KService PRIVATE Qt5::DBus # dbus usage in kcmoduleproxy.cpp Qt5::Qml Qt5::Quick Qt5::QuickWidgets KF5::CoreAddons KF5::I18n KF5::IconThemes # KIconLoader KF5::ItemViews # KWidgetItemDelegate KF5::XmlGui # KAboutApplicationDialog KF5::QuickAddons + KF5::Declarative KF5::Package ) set_target_properties(KF5KCMUtils PROPERTIES VERSION ${KCMUTILS_VERSION_STRING} SOVERSION ${KCMUTILS_SOVERSION} EXPORT_NAME KCMUtils) ecm_generate_headers(KCMUtils_HEADERS HEADER_NAMES KCModuleInfo KCModuleLoader KCMultiDialog KCModuleProxy KPluginSelector KCModuleContainer REQUIRED_HEADERS KCMUtils_HEADERS ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/kcmutils_export.h ${KCMUtils_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KCMUtils COMPONENT Devel ) ecm_generate_headers(KSettings_HEADERS HEADER_NAMES Dispatcher Dialog PluginPage RELATIVE ksettings REQUIRED_HEADERS KSettings_HEADERS ) install(FILES ${KSettings_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KCMUtils/ksettings COMPONENT Devel ) install(TARGETS KF5KCMUtils EXPORT KF5KCMUtilsTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES kcmodule.desktop kcmoduleinit.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR} ) if(BUILD_QCH) ecm_add_qch( KF5KCMUtils_QCH NAME KCMUtils BASE_NAME KF5KCMUtils VERSION ${KF5_VERSION} ORG_DOMAIN org.kde SOURCES # using only public headers, to cover only public API ${KCMUtils_HEADERS} ${KSettings_HEADERS} ksettings/README.dox MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md" LINK_QCHS Qt5Widgets_QCH KF5ConfigWidgets_QCH KF5Service_QCH BLANK_MACROS KCMUTILS_EXPORT KCMUTILS_DEPRECATED KCMUTILS_DEPRECATED_EXPORT TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} COMPONENT Devel ) endif() include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KCMUtils LIB_NAME KF5KCMUtils DEPS "widgets KConfigWidgets KService" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KCMUtils) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/kcmoduleqml.cpp b/src/kcmoduleqml.cpp index 59f266c..2759fb9 100644 --- a/src/kcmoduleqml.cpp +++ b/src/kcmoduleqml.cpp @@ -1,242 +1,259 @@ /* This file is part of the KDE Project Copyright (c) 2014 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kcmoduleqml_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KCModuleQmlPrivate { public: KCModuleQmlPrivate(KQuickAddons::ConfigModule *cm) : quickWindow(nullptr), configModule(cm) { + //ensure the engine is present, then ref it + engine(); + engineRef = s_engine; + } + + ~KCModuleQmlPrivate() + { + //when the only remaining are out two refs, reset the pointers, causing deletion + //when the refcount is 2, we are sure that the only refs are s_engine and our copy + //of engineRef + if (engineRef.use_count() == 2) { + s_engine.reset(); + } + } + + static QQmlEngine *engine() + { + if (!s_engine) { + s_engine = std::make_shared(); + KDeclarative::KDeclarative::setupEngine(s_engine.get()); + } + return s_engine.get(); } QQuickWindow *quickWindow; QQuickWidget *quickWidget; QQuickItem *rootPlaceHolder; KQuickAddons::ConfigModule *configModule; + + //used to delete the engine + std::shared_ptr engineRef; + static std::shared_ptr s_engine; }; +std::shared_ptr KCModuleQmlPrivate::s_engine = std::shared_ptr(); + KCModuleQml::KCModuleQml(KQuickAddons::ConfigModule *configModule, QWidget* parent, const QVariantList& args) : KCModule(parent, args), d(new KCModuleQmlPrivate(configModule)) { connect(configModule, &KQuickAddons::ConfigModule::quickHelpChanged, this, &KCModuleQml::quickHelpChanged); //HACK:Here is important those two enums keep having the exact same values //but the kdeclarative one can't use the KCModule's enum setButtons((KCModule::Buttons)(int)d->configModule->buttons()); connect(configModule, &KQuickAddons::ConfigModule::buttonsChanged, [=] { setButtons((KCModule::Buttons)(int)d->configModule->buttons()); }); if (d->configModule->needsSave()) { emit changed(true); } connect(configModule, &KQuickAddons::ConfigModule::needsSaveChanged, [=] { emit changed(d->configModule->needsSave()); }); setNeedsAuthorization(d->configModule->needsAuthorization()); connect(configModule, &KQuickAddons::ConfigModule::needsAuthorizationChanged, [=] { setNeedsAuthorization(d->configModule->needsAuthorization()); }); setRootOnlyMessage(d->configModule->rootOnlyMessage()); setUseRootOnlyMessage(d->configModule->useRootOnlyMessage()); connect(configModule, &KQuickAddons::ConfigModule::rootOnlyMessageChanged, [=] { setRootOnlyMessage(d->configModule->rootOnlyMessage()); }); connect(configModule, &KQuickAddons::ConfigModule::useRootOnlyMessageChanged, [=] { setUseRootOnlyMessage(d->configModule->useRootOnlyMessage()); }); if (!d->configModule->authActionName().isEmpty()) { setAuthAction(KAuth::Action(d->configModule->authActionName())); } connect(configModule, &KQuickAddons::ConfigModule::authActionNameChanged, [=] { setAuthAction(d->configModule->authActionName()); }); setFocusPolicy(Qt::StrongFocus); -} -KCModuleQml::~KCModuleQml() -{ - delete d; -} -void KCModuleQml::showEvent(QShowEvent *event) -{ - if (d->quickWindow || !d->configModule->mainUi()) { - KCModule::showEvent(event); - return; - } + //Build the UI QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); - d->quickWidget = new QQuickWidget(d->configModule->engine(), this); + d->quickWidget = new QQuickWidget(d->engine(), this); d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView); d->quickWidget->setFocusPolicy(Qt::StrongFocus); d->quickWidget->installEventFilter(this); d->quickWindow = d->quickWidget->quickWindow(); d->quickWindow->setColor(QGuiApplication::palette().window().color()); connect(qApp, &QGuiApplication::paletteChanged, d->quickWindow, [=]() { d->quickWindow->setColor(QGuiApplication::palette().window().color()); }); - QQmlComponent *component = new QQmlComponent(d->configModule->engine(), this); + QQmlComponent *component = new QQmlComponent(d->quickWidget->engine(), this); //this has activeFocusOnTab to notice when the navigation wraps //around, so when we need to go outside and inside //pushPage/popPage are needed as push of StackView can't be directly invoked from c++ //because its parameters are QQmlV4Function which is not public - component->setData(QByteArrayLiteral("import QtQuick 2.3\nimport QtQuick.Controls 2.0\nStackView{function pushPage(page){push(page)}\nfunction popPage(){pop()}\nactiveFocusOnTab:true}"), QUrl()); + component->setData(QByteArrayLiteral("import QtQuick 2.3\nimport org.kde.kirigami 2.4 as Kirigami\nKirigami.ApplicationItem{header:Kirigami.ApplicationHeader{headerStyle:ApplicationHeaderStyle.Breadcrumb;backButtonEnabled:false;background.visible:false}\nfunction __pushPage(page){return pageStack.push(page)}\npageStack.defaultColumnWidth:width\npageStack.separatorVisible:false\nactiveFocusOnTab:true}"), QUrl()); d->rootPlaceHolder = qobject_cast(component->create()); d->quickWidget->setContent(QUrl(), component, d->rootPlaceHolder); - QMetaObject::invokeMethod(d->rootPlaceHolder, "pushPage", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(d->configModule->mainUi()))); + QQmlEngine::setContextForObject(d->configModule, QQmlEngine::contextForObject(d->rootPlaceHolder)); - connect(d->configModule, &KQuickAddons::ConfigModule::newPage, this, [this](QQuickItem *page) { - QMetaObject::invokeMethod(d->rootPlaceHolder, "pushPage", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(page))); - }); - connect(d->configModule, &KQuickAddons::ConfigModule::pageRemoved, this, [this]() { - QMetaObject::invokeMethod(d->rootPlaceHolder, "popPage", Qt::DirectConnection); - }); + QMetaObject::invokeMethod(d->rootPlaceHolder, "__pushPage", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(d->configModule->mainUi()))); layout->addWidget(d->quickWidget); - KCModule::showEvent(event); +} + +KCModuleQml::~KCModuleQml() +{ + delete d; } bool KCModuleQml::eventFilter(QObject* watched, QEvent* event) { //FIXME: those are all workarounds around the QQuickWidget brokeness //BUG https://bugreports.qt.io/browse/QTBUG-64561 if (watched == d->quickWidget && event->type() == QEvent::KeyPress) { //allow tab navigation inside the qquickwidget QKeyEvent *ke = static_cast(event); QQuickItem *currentItem = d->quickWindow->activeFocusItem(); if (!currentItem) { return KCModule::eventFilter(watched, event); } if (currentItem->scopedFocusItem()) { currentItem = currentItem->scopedFocusItem(); } if (ke->key() == Qt::Key_Tab) { //nextItemInFocusChain will always return something, in the worst case will still be currentItem QQuickItem *nextItem = currentItem->nextItemInFocusChain(true); //when it arrives at the place holder item, go out of the qqw and //go to the other widgets around systemsettigns if (nextItem == d->rootPlaceHolder) { QWidget *w = d->quickWidget->nextInFocusChain(); while (!(w->focusPolicy() & Qt::TabFocus)) { w = w->nextInFocusChain(); } w->setFocus(Qt::TabFocusReason); } else { nextItem->forceActiveFocus(Qt::TabFocusReason); } return true; } else if (ke->key() == Qt::Key_Backtab || (ke->key() == Qt::Key_Tab && (ke->modifiers() & Qt::ShiftModifier))) { QQuickItem *nextItem = currentItem->nextItemInFocusChain(false); if (nextItem == d->rootPlaceHolder) { QWidget *w = d->quickWidget->previousInFocusChain(); while (!(w->focusPolicy() & Qt::TabFocus)) { w = w->previousInFocusChain(); } w->setFocus(Qt::BacktabFocusReason); } else { nextItem->forceActiveFocus(Qt::BacktabFocusReason); } return true; } } return KCModule::eventFilter(watched, event); } void KCModuleQml::focusInEvent(QFocusEvent *event) { Q_UNUSED(event) if (event->reason() == Qt::TabFocusReason) { d->rootPlaceHolder->nextItemInFocusChain(true)->forceActiveFocus(Qt::TabFocusReason); } else if (event->reason() == Qt::BacktabFocusReason) { d->rootPlaceHolder->nextItemInFocusChain(false)->forceActiveFocus(Qt::BacktabFocusReason); } } QSize KCModuleQml::sizeHint() const { if (!d->configModule->mainUi()) { return QSize(); } return QSize(d->configModule->mainUi()->implicitWidth(), d->configModule->mainUi()->implicitHeight()); } QString KCModuleQml::quickHelp() const { return d->configModule->quickHelp(); } const KAboutData *KCModuleQml::aboutData() const { return d->configModule->aboutData(); } void KCModuleQml::load() { d->configModule->load(); } void KCModuleQml::save() { d->configModule->save(); d->configModule->setNeedsSave(false); } void KCModuleQml::defaults() { d->configModule->defaults(); } #include "moc_kcmoduleqml_p.cpp" diff --git a/src/kcmoduleqml_p.h b/src/kcmoduleqml_p.h index ea623d8..310fb5f 100644 --- a/src/kcmoduleqml_p.h +++ b/src/kcmoduleqml_p.h @@ -1,57 +1,56 @@ /* Copyright (c) 2014 Marco Martin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KCMODULEQML_H #define KCMODULEQML_H #include class QQuickItem; class KCModuleQmlPrivate; namespace KQuickAddons { class ConfigModule; } class KCModuleQml : public KCModule { Q_OBJECT public: KCModuleQml(KQuickAddons::ConfigModule *configModule, QWidget* parent, const QVariantList& args); ~KCModuleQml(); QString quickHelp() const Q_DECL_OVERRIDE; const KAboutData *aboutData() const Q_DECL_OVERRIDE; public Q_SLOTS: void load() Q_DECL_OVERRIDE; void save() Q_DECL_OVERRIDE; void defaults() Q_DECL_OVERRIDE; protected: - void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; void focusInEvent(QFocusEvent *event) Q_DECL_OVERRIDE; QSize sizeHint() const Q_DECL_OVERRIDE; bool eventFilter(QObject* watched, QEvent* event) Q_DECL_OVERRIDE; private: KCModuleQmlPrivate *const d; }; #endif