File indexing completed on 2024-05-05 05:39:29

0001 /*
0002  *   SPDX-FileCopyrightText: 2009 Ben Cooksley <bcooksley@kde.org>
0003  *   SPDX-FileCopyrightText: 2021 Alexander Lohnau <alexander.lohnau@gmx.de>
0004  *   SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
0005  *
0006  *   SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "SettingsBase.h"
0010 #include "../core/kcmmetadatahelpers.h"
0011 #include "sidebar/SidebarMode.h"
0012 
0013 #include <QFileInfo>
0014 #include <QFontDatabase>
0015 #include <QGuiApplication>
0016 #include <QLoggingCategory>
0017 #include <QMenu>
0018 #include <QMenuBar>
0019 #include <QScreen>
0020 #include <QTimer>
0021 #include <QtGlobal>
0022 
0023 #include <KAboutData>
0024 #include <KActionCollection>
0025 #include <KConfigGroup>
0026 #include <KDesktopFile>
0027 #include <KFileUtils>
0028 #include <KIO/JobUiDelegateFactory>
0029 #include <KIO/OpenUrlJob>
0030 #include <KLocalizedString>
0031 #include <KStandardAction>
0032 #include <KToolBar>
0033 #include <KXMLGUIFactory>
0034 
0035 #include "BaseData.h"
0036 #include "ModuleView.h"
0037 
0038 SettingsBase::SettingsBase(BaseMode::ApplicationMode mode, const QString &startupModule, const QStringList &startupModuleArgs, QWidget *parent)
0039     : KXmlGuiWindow(parent)
0040     , m_mode(mode)
0041     , m_startupModule(startupModule)
0042     , m_startupModuleArgs(startupModuleArgs)
0043 {
0044     // Prepare the view area
0045     stackedWidget = new QStackedWidget(this);
0046     setCentralWidget(stackedWidget);
0047 
0048     setProperty("_breeze_no_separator", true);
0049 
0050     if (m_mode == BaseMode::InfoCenter) {
0051         setWindowTitle(i18nd("systemsettings", "Info Center"));
0052         setWindowIcon(QIcon::fromTheme(QStringLiteral("hwinfo")));
0053     } else {
0054         setWindowTitle(i18nd("systemsettings", "System Settings"));
0055         setWindowIcon(QIcon::fromTheme(QStringLiteral("preferences-system")));
0056     }
0057 
0058     spacerWidget = new QWidget(this);
0059     spacerWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
0060     // Initialise the window so we don't flicker
0061     initToolBar();
0062     // We can now launch the delayed loading safely
0063     initApplication();
0064 }
0065 
0066 SettingsBase::~SettingsBase()
0067 {
0068     delete rootModule;
0069 }
0070 
0071 QSize SettingsBase::sizeHint() const
0072 {
0073     // Take the font size into account for the window size, as we do for UI elements
0074     const float fontSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSizeF();
0075     const QSize targetSize = QSize(qRound(93 * fontSize), qRound(65 * fontSize));
0076 
0077     // on smaller or portrait-rotated screens, do not max out height and/or width
0078     const QSize screenSize = (QGuiApplication::primaryScreen()->availableSize() * 0.9);
0079     return targetSize.boundedTo(screenSize);
0080 }
0081 
0082 void SettingsBase::initApplication()
0083 {
0084     // Prepare the menu of all modules
0085     auto source = m_mode == BaseMode::InfoCenter ? MetaDataSource::KInfoCenter : MetaDataSource::SystemSettings;
0086     pluginModules = findKCMsMetaData(source) << findExternalKCMModules(source);
0087 
0088     const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("categories"), QStandardPaths::LocateDirectory);
0089     categories = KFileUtils::findAllUniqueFiles(dirs, QStringList(QStringLiteral("*.desktop")));
0090 
0091     rootModule = new MenuItem(true, nullptr);
0092     initMenuList(rootModule);
0093 
0094     // Handle lost+found modules...
0095     if (lostFound) {
0096         for (const auto &metaData : std::as_const(pluginModules)) {
0097             auto infoItem = new MenuItem(false, lostFound);
0098             infoItem->setMetaData(metaData);
0099             qCDebug(SYSTEMSETTINGS_APP_LOG) << "Added " << metaData.pluginId();
0100         }
0101     }
0102 
0103     // Prepare the Base Data
0104     BaseData::instance()->setMenuItem(rootModule);
0105     BaseData::instance()->setHomeItem(homeModule);
0106     loadCurrentView();
0107 
0108     // enforce minimum window size
0109     setMinimumSize(SettingsBase::sizeHint());
0110     activateWindow();
0111 
0112     // Change size limit after screen resolution is changed
0113     m_screen = qGuiApp->primaryScreen();
0114     connect(qGuiApp, &QGuiApplication::primaryScreenChanged, this, [this](QScreen *screen) {
0115         if (m_screen) {
0116             disconnect(m_screen, &QScreen::geometryChanged, this, &SettingsBase::slotGeometryChanged);
0117         }
0118         m_screen = screen;
0119         slotGeometryChanged();
0120         connect(m_screen, &QScreen::geometryChanged, this, &SettingsBase::slotGeometryChanged);
0121     });
0122     connect(m_screen, &QScreen::geometryChanged, this, &SettingsBase::slotGeometryChanged);
0123 }
0124 
0125 void SettingsBase::initToolBar()
0126 {
0127     // Fill the toolbar with default actions
0128     // Exit is the very last action
0129     quitAction = actionCollection()->addAction(KStandardAction::Quit, QStringLiteral("quit_action"), this, &QWidget::close);
0130 
0131     if (m_mode == BaseMode::SystemSettings) {
0132         highlightChangesAction = actionCollection()->addAction(QStringLiteral("highlight_changes"), this, [this] {
0133             view->toggleDefaultsIndicatorsVisibility();
0134         });
0135         highlightChangesAction->setCheckable(true);
0136         highlightChangesAction->setText(i18nd("systemsettings", "Highlight Changed Settings"));
0137         highlightChangesAction->setIcon(QIcon::fromTheme(QStringLiteral("draw-highlight")));
0138     }
0139 
0140     reportPageSpecificBugAction = actionCollection()->addAction(QStringLiteral("report_bug_in_current_module"), this, [=] {
0141         const QString bugReportUrlString =
0142             view->moduleView()->activeModuleMetadata().bugReportUrl() + QStringLiteral("&version=") + QGuiApplication::applicationVersion();
0143         auto job = new KIO::OpenUrlJob(QUrl(bugReportUrlString));
0144         job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0145         job->start();
0146     });
0147     reportPageSpecificBugAction->setText(i18nd("systemsettings", "Report a Bug in the Current Pageā€¦"));
0148     reportPageSpecificBugAction->setIcon(QIcon::fromTheme(QStringLiteral("tools-report-bug")));
0149 
0150     // Help after it
0151     initHelpMenu();
0152 
0153     // Then a spacer so the search line-edit is kept separate
0154     spacerAction = new QWidgetAction(this);
0155     spacerAction->setDefaultWidget(spacerWidget);
0156     actionCollection()->addAction(QStringLiteral("spacer"), spacerAction);
0157     // Initialise the Window
0158     setupGUI(Save | Create, QString());
0159     menuBar()->hide();
0160 
0161     // Toolbar & Configuration
0162     helpActionMenu->setMenu(dynamic_cast<QMenu *>(factory()->container(QStringLiteral("help"), this)));
0163     toolBar()->setMovable(false); // We don't allow any changes
0164     changeToolBar(BaseMode::Search | BaseMode::Configure);
0165 }
0166 
0167 void SettingsBase::initHelpMenu()
0168 {
0169     helpActionMenu = new KActionMenu(QIcon::fromTheme(QStringLiteral("help-contents")), i18nd("systemsettings", "Help"), this);
0170     helpActionMenu->setPopupMode(QToolButton::InstantPopup);
0171     actionCollection()->addAction(QStringLiteral("help_toolbar_menu"), helpActionMenu);
0172     // Add the custom actions
0173     aboutViewAction = actionCollection()->addAction(KStandardAction::AboutApp, QStringLiteral("help_about_view"), this, &SettingsBase::about);
0174 }
0175 
0176 void SettingsBase::initMenuList(MenuItem *parent)
0177 {
0178     // look for any categories inside this level, and recurse into them
0179     for (const QString &category : std::as_const(categories)) {
0180         const KDesktopFile file(category);
0181         const KConfigGroup entry = file.desktopGroup();
0182         QString parentCategory;
0183         QString parentCategory2;
0184         if (m_mode == BaseMode::InfoCenter) {
0185             parentCategory = entry.readEntry("X-KDE-KInfoCenter-Parent-Category");
0186         } else {
0187             parentCategory = entry.readEntry("X-KDE-System-Settings-Parent-Category");
0188             parentCategory2 = entry.readEntry("X-KDE-System-Settings-Parent-Category-V2");
0189         }
0190 
0191         if (parentCategory == parent->category() ||
0192             // V2 entries must not be empty if they want to become a proper category.
0193             (!parentCategory2.isEmpty() && parentCategory2 == parent->category())) {
0194             auto menuItem = new MenuItem(true, parent);
0195             menuItem->setCategoryConfig(file);
0196             if (entry.readEntry("X-KDE-System-Settings-Category") == QLatin1String("lost-and-found")) {
0197                 lostFound = menuItem;
0198                 continue;
0199             }
0200             initMenuList(menuItem);
0201         }
0202     }
0203 
0204     // scan for any modules at this level and add them
0205     for (const auto &metaData : std::as_const(pluginModules)) {
0206         QString category;
0207         QString categoryv2;
0208         if (m_mode == BaseMode::InfoCenter) {
0209             category = metaData.value(QStringLiteral("X-KDE-KInfoCenter-Category"));
0210         } else {
0211             category = metaData.value(QStringLiteral("X-KDE-System-Settings-Parent-Category"));
0212             categoryv2 = metaData.value(QStringLiteral("X-KDE-System-Settings-Parent-Category-V2"));
0213         }
0214         const QString parentCategoryKcm = parent->systemsettingsCategoryModule();
0215         bool isCategoryOwner = false;
0216 
0217         if (!parentCategoryKcm.isEmpty() && parentCategoryKcm == metaData.pluginId()) {
0218             parent->setMetaData(metaData);
0219             isCategoryOwner = true;
0220         }
0221 
0222         if (!parent->category().isEmpty() && (category == parent->category() || categoryv2 == parent->category())) {
0223             if (!metaData.isHidden()) {
0224                 // Add the module info to the menu
0225                 auto infoItem = new MenuItem(false, parent);
0226                 infoItem->setMetaData(metaData);
0227                 infoItem->setCategoryOwner(isCategoryOwner);
0228 
0229                 if (m_mode == BaseMode::InfoCenter && metaData.pluginId() == QStringLiteral("kcm_about-distro")) {
0230                     homeModule = infoItem;
0231                 } else if (m_mode == BaseMode::SystemSettings && metaData.pluginId() == QStringLiteral("kcm_landingpage")) {
0232                     homeModule = infoItem;
0233                 }
0234             }
0235         }
0236     }
0237 
0238     parent->sortChildrenByWeight();
0239 }
0240 
0241 bool SettingsBase::queryClose()
0242 {
0243     bool changes = true;
0244     view->saveState();
0245     changes = view->moduleView()->resolveChanges();
0246     return changes;
0247 }
0248 
0249 void SettingsBase::setStartupModule(const QString &startupModule)
0250 {
0251     m_startupModule = startupModule;
0252     view->setStartupModule(startupModule);
0253 }
0254 
0255 void SettingsBase::setStartupModuleArgs(const QStringList &startupModuleArgs)
0256 {
0257     m_startupModuleArgs = startupModuleArgs;
0258 
0259     view->setStartupModuleArgs(startupModuleArgs);
0260 }
0261 
0262 void SettingsBase::reloadStartupModule()
0263 {
0264     view->reloadStartupModule();
0265 }
0266 
0267 void SettingsBase::about()
0268 {
0269     delete aboutDialog;
0270     aboutDialog = nullptr;
0271 
0272     aboutDialog = new KAboutApplicationDialog(KAboutData::applicationData(), nullptr);
0273     aboutDialog->show();
0274 }
0275 
0276 void SettingsBase::loadCurrentView()
0277 {
0278     view = new SidebarMode(this, {m_mode, m_startupModule, m_startupModuleArgs});
0279     connect(view, &BaseMode::changeToolBarItems, this, &SettingsBase::changeToolBar);
0280     connect(view, &BaseMode::actionsChanged, this, &SettingsBase::updateViewActions);
0281     connect(view, &BaseMode::viewChanged, this, &SettingsBase::viewChange);
0282     view->saveState();
0283 
0284     if (stackedWidget->indexOf(view->mainWidget()) == -1) {
0285         stackedWidget->addWidget(view->mainWidget());
0286     }
0287 
0288     // Handle the tooltips
0289     qDeleteAll(tooltipManagers);
0290     tooltipManagers.clear();
0291     const QList<QAbstractItemView *> theViews = view->views();
0292     for (QAbstractItemView *view : theViews) {
0293         tooltipManagers << new ToolTipManager(view);
0294     }
0295 
0296     if (highlightChangesAction) {
0297         highlightChangesAction->setChecked(view->defaultsIndicatorsVisible());
0298     }
0299 
0300     viewChange(false);
0301 
0302     stackedWidget->setCurrentWidget(view->mainWidget());
0303     updateViewActions();
0304 
0305     view->giveFocus();
0306 
0307     // Update visibility of the "report a bug on this page" and "report bug in general"
0308     // actions based on whether the current page has a bug report URL set
0309     auto reportGeneralBugAction = actionCollection()->action(QStringLiteral("help_report_bug"));
0310     if (reportGeneralBugAction) {
0311         reportGeneralBugAction->setVisible(false);
0312     }
0313     auto moduleView = view->moduleView();
0314     connect(moduleView, &ModuleView::moduleChanged, this, [=] {
0315         reportPageSpecificBugAction->setVisible(!moduleView->activeModuleMetadata().bugReportUrl().isEmpty());
0316         if (reportGeneralBugAction) {
0317             reportGeneralBugAction->setVisible(!reportPageSpecificBugAction->isVisible());
0318         }
0319     });
0320 
0321     show();
0322 }
0323 
0324 void SettingsBase::viewChange(bool state)
0325 {
0326     setCaption(view->moduleView()->activeModuleName(), state);
0327 }
0328 
0329 void SettingsBase::updateViewActions()
0330 {
0331     guiFactory()->unplugActionList(this, QStringLiteral("viewActions"));
0332     guiFactory()->plugActionList(this, QStringLiteral("viewActions"), view->actionsList());
0333 }
0334 
0335 void SettingsBase::changeToolBar(BaseMode::ToolBarItems toolbar)
0336 {
0337     if (sender() != view) {
0338         return;
0339     }
0340     guiFactory()->unplugActionList(this, QStringLiteral("configure"));
0341     guiFactory()->unplugActionList(this, QStringLiteral("search"));
0342     guiFactory()->unplugActionList(this, QStringLiteral("quit"));
0343     if (BaseMode::Search & toolbar) {
0344         QList<QAction *> searchBarActions;
0345         searchBarActions << spacerAction;
0346         guiFactory()->plugActionList(this, QStringLiteral("search"), searchBarActions);
0347     }
0348     if (BaseMode::Quit & toolbar) {
0349         QList<QAction *> quitBarActions;
0350         quitBarActions << quitAction;
0351         guiFactory()->plugActionList(this, QStringLiteral("quit"), quitBarActions);
0352     }
0353 
0354     toolBar()->setVisible(toolbar != BaseMode::NoItems || (view->actionsList().count() > 0));
0355 }
0356 
0357 void SettingsBase::slotGeometryChanged()
0358 {
0359     setMinimumSize(SettingsBase::sizeHint());
0360 }
0361 
0362 #include "moc_SettingsBase.cpp"