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"