File indexing completed on 2024-03-24 17:14:00

0001 /*
0002  * SPDX-FileCopyrightText: 2009 Ben Cooksley <bcooksley@kde.org>
0003  * SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
0004  *
0005  * SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "SidebarMode.h"
0009 
0010 #include "BaseData.h"
0011 #include "MenuItem.h"
0012 #include "MenuModel.h"
0013 #include "MenuProxyModel.h"
0014 #include "ModuleView.h"
0015 #include "kcmmetadatahelpers.h"
0016 
0017 #include <QGuiApplication>
0018 #include <QHBoxLayout>
0019 
0020 #include <KAboutData>
0021 #include <KActionCollection>
0022 #include <KCMUtils/KCModuleLoader>
0023 #include <KCModuleInfo>
0024 #include <KConfigGroup>
0025 #include <KDescendantsProxyModel>
0026 #include <KLocalizedContext>
0027 #include <KLocalizedString>
0028 #include <KPackage/Package>
0029 #include <KPackage/PackageLoader>
0030 #include <KXmlGuiWindow>
0031 #include <QAction>
0032 #include <QDebug>
0033 #include <QGraphicsOpacityEffect>
0034 #include <QLabel>
0035 #include <QMenu>
0036 #include <QQmlContext>
0037 #include <QQmlEngine>
0038 #include <QQuickItem>
0039 #include <QQuickWidget>
0040 #include <QStandardItemModel>
0041 #include <QStandardPaths>
0042 
0043 K_PLUGIN_CLASS_WITH_JSON(SidebarMode, "settings-sidebar-view.json")
0044 
0045 FocusHackWidget::FocusHackWidget(QWidget *parent)
0046     : QWidget(parent)
0047 {
0048 }
0049 
0050 FocusHackWidget::~FocusHackWidget()
0051 {
0052 }
0053 
0054 void FocusHackWidget::focusNext()
0055 {
0056     focusNextChild();
0057 }
0058 
0059 void FocusHackWidget::focusPrevious()
0060 {
0061     focusNextPrevChild(false);
0062 }
0063 
0064 SubcategoryModel::SubcategoryModel(QAbstractItemModel *parentModel, SidebarMode *parent)
0065     : KSelectionProxyModel(nullptr, parent)
0066     , m_parentModel(parentModel)
0067     , m_sidebarMode(parent)
0068 {
0069     setSourceModel(parentModel);
0070     setSelectionModel(new QItemSelectionModel(parentModel, this));
0071     setFilterBehavior(SubTreesWithoutRoots);
0072 }
0073 
0074 QString SubcategoryModel::title() const
0075 {
0076     MenuItem *mi = m_activeModuleIndex.data(MenuModel::MenuItemRole).value<MenuItem *>();
0077 
0078     if (!mi) {
0079         return QString();
0080     }
0081 
0082     return mi->name();
0083 }
0084 
0085 QIcon SubcategoryModel::icon() const
0086 {
0087     return m_activeModuleIndex.data(Qt::DecorationRole).value<QIcon>();
0088 }
0089 
0090 bool SubcategoryModel::categoryOwnedByKCM() const
0091 {
0092     return m_activeModuleIndex.data(MenuModel::IsKCMRole).toBool();
0093 }
0094 
0095 void SubcategoryModel::setParentIndex(const QModelIndex &activeModule)
0096 {
0097     selectionModel()->select(activeModule, QItemSelectionModel::ClearAndSelect);
0098     m_activeModuleIndex = QPersistentModelIndex(activeModule);
0099     Q_EMIT titleChanged();
0100     Q_EMIT iconChanged();
0101     Q_EMIT categoryOwnedByKCMChanged();
0102 }
0103 
0104 void SubcategoryModel::loadParentCategoryModule()
0105 {
0106     MenuItem *menuItem = m_activeModuleIndex.data(MenuModel::MenuItemRole).value<MenuItem *>();
0107     if (menuItem->isLibrary()) {
0108         m_sidebarMode->loadModule(m_activeModuleIndex);
0109     }
0110 }
0111 
0112 class SidebarMode::Private
0113 {
0114 public:
0115     Private()
0116         : quickWidget(nullptr)
0117         , moduleView(nullptr)
0118         , collection(nullptr)
0119         , activeCategoryRow(-1)
0120         , activeSubCategoryRow(-1)
0121     {
0122     }
0123 
0124     virtual ~Private()
0125     {
0126         delete aboutIcon;
0127     }
0128 
0129     QQuickWidget *quickWidget = nullptr;
0130     KPackage::Package package;
0131     SubcategoryModel *subCategoryModel = nullptr;
0132     FocusHackWidget *mainWidget = nullptr;
0133     QQuickWidget *placeHolderWidget = nullptr;
0134     QHBoxLayout *mainLayout = nullptr;
0135     MenuModel *model = nullptr;
0136     MenuProxyModel *categorizedModel = nullptr;
0137     MenuProxyModel *searchModel = nullptr;
0138     KDescendantsProxyModel *flatModel = nullptr;
0139     KAboutData *aboutIcon = nullptr;
0140     ModuleView *moduleView = nullptr;
0141     KActionCollection *collection = nullptr;
0142     QPersistentModelIndex activeCategoryIndex;
0143     int activeCategoryRow = -1;
0144     int activeSubCategoryRow = -1;
0145     int activeSearchRow = -1;
0146     qreal headerHeight = 0;
0147     bool m_actionMenuVisible = false;
0148     void setActionMenuVisible(SidebarMode *sidebarMode, const bool &actionMenuVisible)
0149     {
0150         if (m_actionMenuVisible == actionMenuVisible) {
0151             return;
0152         }
0153         m_actionMenuVisible = actionMenuVisible;
0154         Q_EMIT sidebarMode->actionMenuVisibleChanged();
0155     }
0156     bool m_introPageVisible = true;
0157     bool m_defaultsIndicatorsVisible = false;
0158 };
0159 
0160 SidebarMode::SidebarMode(QObject *parent, const QVariantList &args)
0161     : BaseMode(parent, args)
0162     , d(new Private())
0163 {
0164     qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
0165     d->aboutIcon = new KAboutData(QStringLiteral("SidebarView"),
0166                                   i18n("Sidebar View"),
0167                                   QStringLiteral("1.0"),
0168                                   i18n("Provides a categorized sidebar for control modules."),
0169                                   KAboutLicense::GPL,
0170                                   i18n("(c) 2017, Marco Martin"));
0171     d->aboutIcon->addAuthor(i18n("Marco Martin"), i18n("Author"), QStringLiteral("mart@kde.org"));
0172     d->aboutIcon->addAuthor(i18n("Ben Cooksley"), i18n("Author"), QStringLiteral("bcooksley@kde.org"));
0173     d->aboutIcon->addAuthor(i18n("Mathias Soeken"), i18n("Developer"), QStringLiteral("msoeken@informatik.uni-bremen.de"));
0174 
0175     qmlRegisterAnonymousType<QAction>("", 1);
0176     qmlRegisterAnonymousType<QAbstractItemModel>("", 1);
0177 }
0178 
0179 SidebarMode::~SidebarMode()
0180 {
0181     config().sync();
0182     delete d;
0183 }
0184 
0185 KAboutData *SidebarMode::aboutData()
0186 {
0187     return d->aboutIcon;
0188 }
0189 
0190 ModuleView *SidebarMode::moduleView() const
0191 {
0192     return d->moduleView;
0193 }
0194 
0195 QWidget *SidebarMode::mainWidget()
0196 {
0197     if (!d->quickWidget) {
0198         initWidget();
0199     }
0200     return d->mainWidget;
0201 }
0202 
0203 QAbstractItemModel *SidebarMode::categoryModel() const
0204 {
0205     return d->categorizedModel;
0206 }
0207 
0208 QAbstractItemModel *SidebarMode::searchModel() const
0209 {
0210     return d->searchModel;
0211 }
0212 
0213 QAbstractItemModel *SidebarMode::subCategoryModel() const
0214 {
0215     return d->subCategoryModel;
0216 }
0217 
0218 QList<QAbstractItemView *> SidebarMode::views() const
0219 {
0220     QList<QAbstractItemView *> list;
0221     // list.append( d->categoryView );
0222     return list;
0223 }
0224 
0225 void SidebarMode::initEvent()
0226 {
0227     d->model = new MenuModel(rootItem(), this);
0228     foreach (MenuItem *child, rootItem()->children()) {
0229         d->model->addException(child);
0230     }
0231 
0232     d->categorizedModel = new MenuProxyModel(this);
0233     d->categorizedModel->setCategorizedModel(true);
0234     d->categorizedModel->setSourceModel(d->model);
0235     d->categorizedModel->sort(0);
0236     d->categorizedModel->setFilterHighlightsEntries(false);
0237 
0238     d->flatModel = new KDescendantsProxyModel(this);
0239     d->flatModel->setSourceModel(d->model);
0240 
0241     d->searchModel = new MenuProxyModel(this);
0242     d->searchModel->setCategorizedModel(true);
0243     d->searchModel->setFilterHighlightsEntries(false);
0244     d->searchModel->setSourceModel(d->flatModel);
0245 
0246     d->subCategoryModel = new SubcategoryModel(d->categorizedModel, this);
0247     d->mainWidget = new FocusHackWidget();
0248     d->mainWidget->installEventFilter(this);
0249     d->mainLayout = new QHBoxLayout(d->mainWidget);
0250     d->mainLayout->setContentsMargins(0, 0, 0, 0);
0251     d->mainLayout->setSpacing(0);
0252     d->moduleView = new ModuleView(d->mainWidget);
0253     connect(d->moduleView, &ModuleView::moduleChanged, this, &SidebarMode::moduleLoaded);
0254     connect(d->moduleView, &ModuleView::moduleSaved, this, &SidebarMode::updateDefaults);
0255     d->quickWidget = nullptr;
0256     moduleView()->setFaceType(KPageView::Plain);
0257     if (applicationMode() == BaseMode::InfoCenter) {
0258         d->moduleView->setSaveStatistics(false);
0259         d->moduleView->setApplyVisible(false);
0260         d->moduleView->setDefaultsVisible(false);
0261     }
0262 
0263     if (config().readEntry("HighlightNonDefaultSettings", false)) {
0264         toggleDefaultsIndicatorsVisibility();
0265     }
0266 }
0267 
0268 QAction *SidebarMode::action(const QString &name) const
0269 {
0270     if (!d->collection) {
0271         return nullptr;
0272     }
0273 
0274     return d->collection->action(name);
0275 }
0276 
0277 QString SidebarMode::actionIconName(const QString &name) const
0278 {
0279     if (QAction *a = action(name)) {
0280         return a->icon().name();
0281     }
0282 
0283     return QString();
0284 }
0285 
0286 void SidebarMode::showActionMenu(const QPoint &position)
0287 {
0288     QMenu *menu = new QMenu();
0289     connect(menu, &QMenu::aboutToHide, this, [this]() {
0290         d->setActionMenuVisible(this, false);
0291     });
0292     menu->setAttribute(Qt::WA_DeleteOnClose);
0293 
0294     const QStringList actionList{QStringLiteral("switchto_iconview"),
0295                                  QStringLiteral("highlight_changes"),
0296                                  QStringLiteral("configure"),
0297                                  QStringLiteral("report_bug_in_current_module"),
0298                                  QStringLiteral("help_report_bug"),
0299                                  QStringLiteral("help_contents"),
0300                                  QStringLiteral("help_about_app"),
0301                                  QStringLiteral("help_about_kde")};
0302 
0303     for (const QString &actionName : actionList) {
0304         QAction *action = d->collection->action(actionName);
0305         if (action) {
0306             menu->addAction(action);
0307         }
0308     }
0309 
0310     menu->popup(position);
0311     menu->windowHandle()->setTransientParent(d->quickWidget->window()->windowHandle());
0312     d->setActionMenuVisible(this, true);
0313 }
0314 
0315 void SidebarMode::loadModule(const QModelIndex &activeModule, const QStringList &args)
0316 {
0317     if (!activeModule.isValid()) {
0318         return;
0319     }
0320 
0321     MenuItem *mi = activeModule.data(MenuModel::MenuItemRole).value<MenuItem *>();
0322 
0323     if (!mi) {
0324         return;
0325     }
0326 
0327     // If we are trying to load a module already open
0328     if (mi->name() == d->moduleView->activeModuleName()) {
0329         return;
0330     }
0331 
0332     if (!d->moduleView->resolveChanges()) {
0333         return;
0334     }
0335 
0336     d->moduleView->closeModules();
0337 
0338     if (homeItem()) {
0339         d->m_introPageVisible = activeModule == d->categorizedModel->mapFromSource(d->model->indexForItem(homeItem()));
0340         Q_EMIT introPageVisibleChanged();
0341     } else {
0342         setIntroPageVisible(false);
0343     }
0344 
0345     d->moduleView->loadModule(activeModule, args);
0346 
0347     if (activeModule.model() == d->categorizedModel) {
0348         const int newCategoryRow = activeModule.row();
0349 
0350         d->activeCategoryIndex = activeModule;
0351         d->activeCategoryRow = newCategoryRow;
0352 
0353         d->activeSubCategoryRow = 0;
0354 
0355         d->subCategoryModel->setParentIndex(activeModule);
0356 
0357         if (d->activeSearchRow > -1) {
0358             d->activeSearchRow = -1;
0359             Q_EMIT activeSearchRowChanged();
0360         }
0361         Q_EMIT activeCategoryRowChanged();
0362         Q_EMIT activeSubCategoryRowChanged();
0363 
0364     } else if (activeModule.model() == d->subCategoryModel) {
0365         if (d->activeSearchRow > -1) {
0366             d->activeSearchRow = -1;
0367             Q_EMIT activeSearchRowChanged();
0368         }
0369         d->activeSubCategoryRow = activeModule.row();
0370         Q_EMIT activeSubCategoryRowChanged();
0371 
0372     } else if (activeModule.model() == d->searchModel) {
0373         QModelIndex originalIndex = d->categorizedModel->mapFromSource(d->flatModel->mapToSource(d->searchModel->mapToSource(activeModule)));
0374 
0375         if (originalIndex.isValid()) {
0376             // are we in a  subcategory of the top categories?
0377             if (originalIndex.parent().isValid() && mi->parent()->menu()) {
0378                 d->activeCategoryRow = originalIndex.parent().row();
0379                 d->activeSubCategoryRow = originalIndex.row();
0380 
0381                 // Is this kcm directly at the top level without a top category?
0382             } else {
0383                 d->activeCategoryRow = originalIndex.row();
0384                 d->activeSubCategoryRow = -1;
0385             }
0386 
0387             d->subCategoryModel->setParentIndex(originalIndex.parent().isValid() ? originalIndex.parent() : originalIndex);
0388             Q_EMIT activeCategoryRowChanged();
0389             Q_EMIT activeSubCategoryRowChanged();
0390         }
0391 
0392         d->activeSearchRow = activeModule.row();
0393         Q_EMIT activeSearchRowChanged();
0394 
0395     } else {
0396         if (d->activeSearchRow > -1) {
0397             d->activeSearchRow = -1;
0398             Q_EMIT activeSearchRowChanged();
0399         }
0400 
0401         QModelIndex flatIndex;
0402 
0403         // search the corresponding item on the main model
0404         for (int i = 0; i < d->flatModel->rowCount(); ++i) {
0405             QModelIndex idx = d->flatModel->index(i, 0);
0406             MenuItem *otherMi = idx.data(MenuModel::MenuItemRole).value<MenuItem *>();
0407 
0408             if (mi->metaData().isValid() && otherMi->metaData() == mi->metaData()) {
0409                 flatIndex = idx;
0410                 break;
0411             }
0412         }
0413 
0414         if (flatIndex.isValid()) {
0415             QModelIndex idx = d->categorizedModel->mapFromSource(d->flatModel->mapToSource(flatIndex));
0416 
0417             MenuItem *parentMi = idx.parent().data(MenuModel::MenuItemRole).value<MenuItem *>();
0418 
0419             if (idx.isValid()) {
0420                 if (parentMi && parentMi->menu()) {
0421                     d->subCategoryModel->setParentIndex(idx.parent());
0422                     d->activeCategoryRow = idx.parent().row();
0423                     d->activeSubCategoryRow = idx.row();
0424                 } else {
0425                     if (d->categorizedModel->rowCount(idx) > 0) {
0426                         d->subCategoryModel->setParentIndex(idx);
0427                     }
0428                     d->activeCategoryRow = idx.row();
0429                     d->activeSubCategoryRow = -1;
0430                 }
0431                 Q_EMIT activeCategoryRowChanged();
0432                 Q_EMIT activeSubCategoryRowChanged();
0433             }
0434         }
0435     }
0436 }
0437 
0438 void SidebarMode::moduleLoaded()
0439 {
0440     if (d->placeHolderWidget) {
0441         d->placeHolderWidget->hide();
0442     }
0443     d->moduleView->show();
0444     if (applicationMode() == BaseMode::InfoCenter) {
0445         d->moduleView->setSaveStatistics(false);
0446         d->moduleView->setApplyVisible(false);
0447         d->moduleView->setDefaultsVisible(false);
0448     }
0449 }
0450 
0451 void SidebarMode::updateDefaults()
0452 {
0453     // When the landing page is loaded, we don't have activeCategoryRow and need to reload everything
0454     if (d->activeCategoryRow < 0) {
0455         refreshDefaults();
0456         return;
0457     }
0458     QModelIndex categoryIdx = d->categorizedModel->index(d->activeCategoryRow, 0);
0459     auto item = categoryIdx.data(Qt::UserRole).value<MenuItem *>();
0460     Q_ASSERT(item);
0461 
0462     // If subcategory exist update from subcategory, unless this category is owned by a kcm
0463     if (!item->children().isEmpty() && d->activeSubCategoryRow > -1) {
0464         auto subCateogryIdx = d->subCategoryModel->index(d->activeSubCategoryRow, 0);
0465         item = subCateogryIdx.data(Qt::UserRole).value<MenuItem *>();
0466     }
0467     // In case we are updating a parent like LnF we refresh all defaults
0468     if (item->isCategoryOwner() && item->parent() != rootItem()) {
0469         refreshDefaults();
0470         return;
0471     }
0472 
0473     KCModuleData *moduleData = loadModuleData(item->metaData());
0474     if (moduleData) {
0475         connect(moduleData, &KCModuleData::loaded, this, [this, item, moduleData, categoryIdx]() {
0476             item->setDefaultIndicator(!moduleData->isDefaults());
0477             updateCategoryModel(categoryIdx);
0478             moduleData->deleteLater();
0479         });
0480     }
0481 }
0482 
0483 void SidebarMode::updateCategoryModel(const QModelIndex &categoryIdx)
0484 {
0485     auto sourceIdx = d->categorizedModel->mapToSource(categoryIdx);
0486     Q_EMIT d->model->dataChanged(sourceIdx, sourceIdx);
0487 
0488     auto subCateogryIdx = d->subCategoryModel->index(d->activeSubCategoryRow, 0);
0489     auto subCategorySourceIdx = d->categorizedModel->mapToSource(d->subCategoryModel->mapToSource(subCateogryIdx));
0490     Q_EMIT d->model->dataChanged(subCategorySourceIdx, subCategorySourceIdx);
0491 }
0492 
0493 int SidebarMode::activeSearchRow() const
0494 {
0495     return d->activeSearchRow;
0496 }
0497 
0498 int SidebarMode::activeCategoryRow() const
0499 {
0500     return d->activeCategoryRow;
0501 }
0502 
0503 void SidebarMode::setIntroPageVisible(const bool &introPageVisible)
0504 {
0505     if (d->m_introPageVisible == introPageVisible) {
0506         return;
0507     }
0508 
0509     // TODO: Make the intro page of SystemSettings a KCM as well
0510     if (homeItem()) {
0511         if (d->placeHolderWidget) {
0512             d->placeHolderWidget->hide();
0513         }
0514         d->moduleView->show();
0515         if (introPageVisible) {
0516             QModelIndex index = d->categorizedModel->mapFromSource(d->model->indexForItem(homeItem()));
0517             if (index.isValid()) {
0518                 loadModule(index);
0519             } else {
0520                 d->moduleView->closeModules();
0521                 d->moduleView->loadModule( d->model->indexForItem(homeItem()), QStringList() );
0522                 d->activeCategoryRow = -1;
0523                 d->activeSubCategoryRow = -1;
0524                 Q_EMIT activeCategoryRowChanged();
0525                 Q_EMIT activeSubCategoryRowChanged();
0526             }
0527         }
0528     } else {
0529         if (introPageVisible) {
0530             d->subCategoryModel->setParentIndex(QModelIndex());
0531             d->activeCategoryRow = -1;
0532             Q_EMIT activeCategoryRowChanged();
0533             d->activeSubCategoryRow = -1;
0534             Q_EMIT activeSubCategoryRowChanged();
0535             if (!d->placeHolderWidget) {
0536                 initPlaceHolderWidget();
0537             }
0538             d->placeHolderWidget->show();
0539             d->moduleView->hide();
0540         } else {
0541             if (d->placeHolderWidget) {
0542                 d->placeHolderWidget->hide();
0543             }
0544             d->moduleView->show();
0545         }
0546     }
0547 
0548     d->m_introPageVisible = introPageVisible;
0549     Q_EMIT introPageVisibleChanged();
0550 }
0551 
0552 void SidebarMode::setHeaderHeight(qreal height)
0553 {
0554     if (height == d->moduleView->headerHeight()) {
0555         return;
0556     }
0557 
0558     d->moduleView->setHeaderHeight(height);
0559     Q_EMIT headerHeightChanged();
0560 }
0561 
0562 qreal SidebarMode::headerHeight() const
0563 {
0564     return d->moduleView->headerHeight();
0565 }
0566 
0567 void SidebarMode::refreshDefaults()
0568 {
0569     if (d->m_defaultsIndicatorsVisible) {
0570         for (int i = 0; i < d->flatModel->rowCount(); ++i) {
0571             QModelIndex idx = d->flatModel->index(i, 0);
0572             auto item = idx.data(MenuModel::MenuItemRole).value<MenuItem *>();
0573             if (item->menu()) {
0574                 continue;
0575             }
0576 
0577             KCModuleData *moduleData = loadModuleData(item->metaData());
0578             if (moduleData) {
0579                 connect(moduleData, &KCModuleData::loaded, this, [this, item, moduleData]() {
0580                     item->setDefaultIndicator(!moduleData->isDefaults());
0581                     updateModelMenuItem(item);
0582                     moduleData->deleteLater();
0583                 });
0584             }
0585         }
0586     }
0587 }
0588 
0589 void SidebarMode::toggleDefaultsIndicatorsVisibility()
0590 {
0591     d->m_defaultsIndicatorsVisible = !d->m_defaultsIndicatorsVisible;
0592     d->moduleView->moduleShowDefaultsIndicators(d->m_defaultsIndicatorsVisible);
0593     refreshDefaults();
0594     config().writeEntry("HighlightNonDefaultSettings", d->m_defaultsIndicatorsVisible);
0595     Q_EMIT defaultsIndicatorsVisibleChanged();
0596 }
0597 
0598 void SidebarMode::updateModelMenuItem(MenuItem *item)
0599 {
0600     auto itemIdx = d->model->indexForItem(item);
0601     Q_EMIT d->model->dataChanged(itemIdx, itemIdx);
0602     MenuItem *parent = item->parent();
0603     while (parent) {
0604         auto parentIdx = d->model->indexForItem(parent);
0605         if (parentIdx.isValid()) {
0606             Q_EMIT d->model->dataChanged(parentIdx, parentIdx);
0607             parent = parent->parent();
0608         } else {
0609             parent = nullptr;
0610         }
0611     }
0612 }
0613 
0614 int SidebarMode::width() const
0615 {
0616     return d->mainWidget->width();
0617 }
0618 
0619 bool SidebarMode::actionMenuVisible() const
0620 {
0621     return d->m_actionMenuVisible;
0622 }
0623 
0624 int SidebarMode::activeSubCategoryRow() const
0625 {
0626     return d->activeSubCategoryRow;
0627 }
0628 
0629 bool SidebarMode::introPageVisible() const
0630 {
0631     return (d->m_introPageVisible);
0632 }
0633 
0634 bool SidebarMode::defaultsIndicatorsVisible() const
0635 {
0636     return d->m_defaultsIndicatorsVisible;
0637 }
0638 
0639 void SidebarMode::initWidget()
0640 {
0641     // Create the widgets
0642 
0643     if (!KMainWindow::memberList().isEmpty()) {
0644         KXmlGuiWindow *mainWindow = qobject_cast<KXmlGuiWindow *>(KMainWindow::memberList().first());
0645         if (mainWindow) {
0646             d->collection = mainWindow->actionCollection();
0647         }
0648     }
0649 
0650     d->quickWidget = new QQuickWidget(d->mainWidget);
0651     d->quickWidget->quickWindow()->setTitle(i18n("Sidebar"));
0652     d->quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
0653     qmlRegisterUncreatableType<SidebarMode>(
0654         "org.kde.systemsettings", 1, 0, "SystemSettings", QStringLiteral("Not creatable, use the systemsettings attached property"));
0655 
0656     d->quickWidget->engine()->rootContext()->setContextProperty(QStringLiteral("systemsettings"), this);
0657     d->package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/GenericQML"));
0658     d->package.setPath(QStringLiteral("org.kde.systemsettings.sidebar"));
0659 
0660     d->quickWidget->engine()->rootContext()->setContextObject(new KLocalizedContext(d->quickWidget));
0661 
0662     d->quickWidget->setSource(QUrl::fromLocalFile(d->package.filePath("mainscript")));
0663 
0664     if (!d->quickWidget->rootObject()) {
0665         for (const auto &err : d->quickWidget->errors()) {
0666             qWarning() << err.toString();
0667         }
0668         qFatal("Fatal error while loading the sidebar view qml component");
0669     }
0670     const int rootImplicitWidth = d->quickWidget->rootObject()->property("implicitWidth").toInt();
0671     if (rootImplicitWidth != 0) {
0672         d->quickWidget->setFixedWidth(rootImplicitWidth);
0673     } else {
0674         d->quickWidget->setFixedWidth(240);
0675     }
0676     connect(d->quickWidget->rootObject(), &QQuickItem::implicitWidthChanged, this, [this]() {
0677         const int rootImplicitWidth = d->quickWidget->rootObject()->property("implicitWidth").toInt();
0678         if (rootImplicitWidth != 0) {
0679             d->quickWidget->setFixedWidth(rootImplicitWidth);
0680         } else {
0681             d->quickWidget->setFixedWidth(240);
0682         }
0683     });
0684 
0685     setHeaderHeight(d->quickWidget->rootObject()->property("headerHeight").toReal());
0686 
0687     connect(d->quickWidget->rootObject(), SIGNAL(focusNextRequest()), d->mainWidget, SLOT(focusNext()));
0688     connect(d->quickWidget->rootObject(), SIGNAL(focusPreviousRequest()), d->mainWidget, SLOT(focusPrevious()));
0689 
0690     d->quickWidget->installEventFilter(this);
0691 
0692     d->mainLayout->addWidget(d->quickWidget);
0693     d->moduleView->hide();
0694     d->mainLayout->addWidget(d->moduleView);
0695     Q_EMIT changeToolBarItems(BaseMode::NoItems);
0696 
0697     if (!startupModule().isEmpty()) {
0698         initPlaceHolderWidget();
0699         d->placeHolderWidget->show();
0700         MenuItem *item = rootItem()->descendantForModule(startupModule());
0701         if (item) {
0702             loadModule(d->model->indexForItem(item), startupModuleArgs());
0703         }
0704     } else if (homeItem()) {
0705         d->moduleView->show();
0706         QModelIndex index = d->categorizedModel->mapFromSource(d->model->indexForItem(homeItem()));
0707         if (index.isValid()) {
0708             loadModule(index);
0709         } else {
0710             d->moduleView->loadModule( d->model->indexForItem(homeItem()), QStringList() );
0711         }
0712     } else {
0713         initPlaceHolderWidget();
0714         d->placeHolderWidget->show();
0715     }
0716 }
0717 
0718 void SidebarMode::initPlaceHolderWidget()
0719 {
0720     d->placeHolderWidget = new QQuickWidget(d->mainWidget);
0721     d->placeHolderWidget->quickWindow()->setTitle(i18n("Most Used"));
0722     d->placeHolderWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
0723     d->placeHolderWidget->engine()->rootContext()->setContextObject(new KLocalizedContext(d->placeHolderWidget));
0724     d->placeHolderWidget->engine()->rootContext()->setContextProperty(QStringLiteral("systemsettings"), this);
0725     d->placeHolderWidget->setSource(QUrl::fromLocalFile(d->package.filePath("ui", QStringLiteral("introPage.qml"))));
0726     connect(d->placeHolderWidget->rootObject(), SIGNAL(focusNextRequest()), d->mainWidget, SLOT(focusNext()));
0727     connect(d->placeHolderWidget->rootObject(), SIGNAL(focusPreviousRequest()), d->mainWidget, SLOT(focusPrevious()));
0728     d->placeHolderWidget->installEventFilter(this);
0729 
0730     d->mainLayout->addWidget(d->placeHolderWidget);
0731 }
0732 
0733 void SidebarMode::reloadStartupModule()
0734 {
0735     if (!startupModule().isEmpty()) {
0736         MenuItem *item = rootItem()->descendantForModule(startupModule());
0737         if (item) {
0738             loadModule(d->model->indexForItem(item), startupModuleArgs());
0739         }
0740     }
0741 }
0742 
0743 bool SidebarMode::eventFilter(QObject *watched, QEvent *event)
0744 {
0745     // FIXME: those are all workarounds around the QQuickWidget brokenness
0746     if ((watched == d->quickWidget || watched == d->placeHolderWidget) && event->type() == QEvent::KeyPress) {
0747         // allow tab navigation inside the qquickwidget
0748         QKeyEvent *ke = static_cast<QKeyEvent *>(event);
0749         QQuickWidget *qqw = static_cast<QQuickWidget *>(watched);
0750         if (ke->key() == Qt::Key_Tab || ke->key() == Qt::Key_Backtab) {
0751             QCoreApplication::sendEvent(qqw->quickWindow(), event);
0752             return true;
0753         }
0754     } else if ((watched == d->quickWidget || watched == d->placeHolderWidget) && event->type() == QEvent::FocusIn) {
0755         QFocusEvent *fe = static_cast<QFocusEvent *>(event);
0756         QQuickWidget *qqw = static_cast<QQuickWidget *>(watched);
0757         if (qqw && qqw->rootObject()) {
0758             if (fe->reason() == Qt::TabFocusReason) {
0759                 QMetaObject::invokeMethod(qqw->rootObject(), "focusFirstChild");
0760             } else if (fe->reason() == Qt::BacktabFocusReason) {
0761                 QMetaObject::invokeMethod(qqw->rootObject(), "focusLastChild");
0762             }
0763         }
0764     } else if (watched == d->quickWidget && event->type() == QEvent::Leave) {
0765         QCoreApplication::sendEvent(d->quickWidget->quickWindow(), event);
0766     } else if (watched == d->mainWidget && event->type() == QEvent::Resize) {
0767         Q_EMIT widthChanged();
0768     } else if (watched == d->mainWidget && event->type() == QEvent::Show) {
0769         Q_EMIT changeToolBarItems(BaseMode::NoItems);
0770     }
0771     return BaseMode::eventFilter(watched, event);
0772 }
0773 
0774 void SidebarMode::giveFocus()
0775 {
0776     d->quickWidget->setFocus();
0777 }
0778 
0779 #include "SidebarMode.moc"