File indexing completed on 2024-05-12 04:42:02
0001 /* 0002 SPDX-FileCopyrightText: 2015 Gregor Mi <codestruct@posteo.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-or-later 0005 */ 0006 0007 #include "kmoretools.h" 0008 0009 #include "kmoretools_debug.h" 0010 #include "kmoretools_p.h" 0011 #include "kmoretoolsconfigdialog_p.h" 0012 0013 #include <QApplication> 0014 #include <QDebug> 0015 #include <QStandardPaths> 0016 0017 #include <KConfig> 0018 #include <KConfigGroup> 0019 #include <KLocalizedString> 0020 #include <optional> 0021 0022 class KMoreToolsPrivate 0023 { 0024 public: 0025 QString uniqueId; 0026 0027 // allocated via new, don't forget to delete 0028 QList<KMoreToolsService *> serviceList; 0029 0030 QMap<QString, KMoreToolsMenuBuilder *> menuBuilderMap; 0031 0032 public: 0033 KMoreToolsPrivate(const QString &uniqueId) 0034 : uniqueId(uniqueId) 0035 { 0036 } 0037 0038 ~KMoreToolsPrivate() 0039 { 0040 qDeleteAll(menuBuilderMap); 0041 qDeleteAll(serviceList); 0042 } 0043 0044 /** 0045 * @return uniqueId if kmtDesktopfileSubdir is empty 0046 * else kmtDesktopfileSubdir 0047 */ 0048 QString kmtDesktopfileSubdirOrUniqueId(const QString &kmtDesktopfileSubdir) 0049 { 0050 if (kmtDesktopfileSubdir.isEmpty()) { 0051 return uniqueId; 0052 } 0053 0054 return kmtDesktopfileSubdir; 0055 } 0056 0057 /** 0058 * Finds a file in the '/usr/share'/kf5/kmoretools/'uniqueId'/ directory. 0059 * '/usr/share' = "~/.local/share", "/usr/local/share", "/usr/share" (see QStandardPaths::GenericDataLocation) 0060 * 'uniqueId' = @see uniqueId() 0061 * 0062 * @param can be a filename with or without relative path. But no absolute path. 0063 * @returns the first occurrence if there are more than one found 0064 */ 0065 QString findFileInKmtDesktopfilesDir(const QString &filename) 0066 { 0067 return findFileInKmtDesktopfilesDir(uniqueId, filename); 0068 } 0069 0070 static QString findFileInKmtDesktopfilesDir(const QString &kmtDesktopfileSubdir, const QString &filename) 0071 { 0072 const QString kmtDesktopfilesFilename = QLatin1String("kf6/kmoretools/") + kmtDesktopfileSubdir + QLatin1Char('/') + filename; 0073 const QString foundKmtFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, kmtDesktopfilesFilename); 0074 0075 return foundKmtFile; 0076 } 0077 }; 0078 0079 KMoreTools::KMoreTools(const QString &uniqueId) 0080 : d(new KMoreToolsPrivate(uniqueId)) 0081 { 0082 } 0083 0084 KMoreTools::~KMoreTools() = default; 0085 0086 KMoreToolsService *KMoreTools::registerServiceByDesktopEntryName(const QString &desktopEntryName, 0087 const QString &kmtDesktopfileSubdir, 0088 KMoreTools::ServiceLocatingMode serviceLocatingMode) 0089 { 0090 const QString foundKmtDesktopfilePath = 0091 d->findFileInKmtDesktopfilesDir(d->kmtDesktopfileSubdirOrUniqueId(kmtDesktopfileSubdir), desktopEntryName + QLatin1String(".desktop")); 0092 const bool isKmtDesktopfileProvided = !foundKmtDesktopfilePath.isEmpty(); 0093 0094 KService::Ptr kmtDesktopfile; 0095 0096 if (isKmtDesktopfileProvided) { 0097 kmtDesktopfile = KService::Ptr(new KService(foundKmtDesktopfilePath)); 0098 if (!kmtDesktopfile->isValid()) { 0099 qCCritical(KMORETOOLS) << "KMoreTools::registerServiceByDesktopEntryName: the kmt-desktopfile " << desktopEntryName 0100 << " is provided but no Exec line is specified. The desktop file is probably faulty. Please fix. Return nullptr."; 0101 return nullptr; 0102 } 0103 } else { 0104 qCWarning(KMORETOOLS) << "KMoreTools::registerServiceByDesktopEntryName: desktopEntryName " << desktopEntryName 0105 << " (kmtDesktopfileSubdir=" << kmtDesktopfileSubdir 0106 << ") not provided (or at the wrong place) in the installed kmt-desktopfiles directory. If the service is also not installed on " 0107 "the system the user won't get nice translated app name and description."; 0108 qCDebug(KMORETOOLS) << "`-- More info at findFileInKmtDesktopfilesDir, QStandardPaths::standardLocations = " 0109 << QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); // /usr/share etc. 0110 } 0111 0112 bool isInstalled = false; 0113 KService::Ptr installedService; 0114 if (serviceLocatingMode == KMoreTools::ServiceLocatingMode_Default) { // == default behaviour: search for installed services 0115 installedService = KService::serviceByDesktopName(desktopEntryName); 0116 isInstalled = installedService != nullptr; 0117 } else if (serviceLocatingMode == KMoreTools::ServiceLocatingMode_ByProvidedExecLine) { // only use provided kmt-desktopfile: 0118 if (!isKmtDesktopfileProvided) { 0119 qCCritical(KMORETOOLS) 0120 << "KMoreTools::registerServiceByDesktopEntryName for " << desktopEntryName 0121 << ": If detectServiceExistenceViaProvidedExecLine is true then a kmt-desktopfile must be provided. Please fix. Return nullptr."; 0122 return nullptr; 0123 } 0124 0125 const QString tryExec = kmtDesktopfile->property<QString>(QStringLiteral("TryExec")); 0126 isInstalled = 0127 (!tryExec.isEmpty() && !QStandardPaths::findExecutable(tryExec).isEmpty()) || !QStandardPaths::findExecutable(kmtDesktopfile->exec()).isEmpty(); 0128 } else { 0129 Q_UNREACHABLE(); // case not handled 0130 } 0131 0132 auto registeredService = 0133 new KMoreToolsService(d->kmtDesktopfileSubdirOrUniqueId(kmtDesktopfileSubdir), desktopEntryName, isInstalled, installedService, kmtDesktopfile); 0134 0135 // add or replace item in serviceList 0136 auto foundService = std::find_if(d->serviceList.begin(), d->serviceList.end(), [&desktopEntryName](KMoreToolsService *service) { 0137 return service->desktopEntryName() == desktopEntryName; 0138 }); 0139 if (foundService == d->serviceList.end()) { 0140 d->serviceList.append(registeredService); 0141 } else { 0142 KMoreToolsService *foundServicePtr = *foundService; 0143 int i = d->serviceList.indexOf(foundServicePtr); 0144 delete foundServicePtr; 0145 d->serviceList.replace(i, registeredService); 0146 } 0147 0148 return registeredService; 0149 } 0150 0151 KMoreToolsMenuBuilder *KMoreTools::menuBuilder(const QString &userConfigPostfix) const 0152 { 0153 if (d->menuBuilderMap.find(userConfigPostfix) == d->menuBuilderMap.end()) { 0154 d->menuBuilderMap.insert(userConfigPostfix, new KMoreToolsMenuBuilder(d->uniqueId, userConfigPostfix)); 0155 } 0156 return d->menuBuilderMap[userConfigPostfix]; 0157 } 0158 0159 // ------------------------------------------------------------------------------------------------ 0160 // ------------------------------------------------------------------------------------------------ 0161 0162 class KMoreToolsServicePrivate 0163 { 0164 public: 0165 QString kmtDesktopfileSubdir; 0166 QString desktopEntryName; 0167 KService::Ptr installedService; 0168 KService::Ptr kmtDesktopfile; 0169 QUrl homepageUrl; 0170 int maxUrlArgCount = 0; 0171 bool isInstalled = false; 0172 QString appstreamId; 0173 0174 public: 0175 QString getServiceName() 0176 { 0177 if (installedService) { 0178 return installedService->name(); 0179 } else { 0180 if (kmtDesktopfile) { 0181 return kmtDesktopfile->name(); 0182 } else { 0183 return QString(); 0184 } 0185 } 0186 } 0187 0188 QString getServiceGenericName() 0189 { 0190 if (installedService) { 0191 return installedService->genericName(); 0192 } else { 0193 if (kmtDesktopfile) { 0194 return kmtDesktopfile->genericName(); 0195 } else { 0196 return QString(); 0197 } 0198 } 0199 } 0200 0201 /** 0202 * @return the provided icon or an empty icon if not kmtDesktopfile is available or the icon was not found 0203 */ 0204 QIcon getKmtProvidedIcon() 0205 { 0206 if (!kmtDesktopfile) { 0207 return QIcon(); 0208 } 0209 0210 QString iconPath = KMoreToolsPrivate::findFileInKmtDesktopfilesDir(kmtDesktopfileSubdir, kmtDesktopfile->icon() + QLatin1String(".svg")); 0211 QIcon svgIcon(iconPath); 0212 if (!svgIcon.isNull()) { 0213 return svgIcon; 0214 } 0215 0216 iconPath = KMoreToolsPrivate::findFileInKmtDesktopfilesDir(kmtDesktopfileSubdir, kmtDesktopfile->icon() + QLatin1String(".png")); 0217 QIcon pngIcon(iconPath); 0218 if (!pngIcon.isNull()) { 0219 return pngIcon; 0220 } 0221 0222 return QIcon(); 0223 } 0224 }; 0225 0226 KMoreToolsService::KMoreToolsService(const QString &kmtDesktopfileSubdir, 0227 const QString &desktopEntryName, 0228 bool isInstalled, 0229 KService::Ptr installedService, 0230 KService::Ptr kmtDesktopfile) 0231 : d(new KMoreToolsServicePrivate()) 0232 { 0233 d->kmtDesktopfileSubdir = kmtDesktopfileSubdir; 0234 d->desktopEntryName = desktopEntryName; 0235 d->isInstalled = isInstalled; 0236 d->installedService = installedService; 0237 d->kmtDesktopfile = kmtDesktopfile; 0238 } 0239 0240 KMoreToolsService::~KMoreToolsService() = default; 0241 0242 QString KMoreToolsService::desktopEntryName() const 0243 { 0244 return d->desktopEntryName; 0245 } 0246 0247 bool KMoreToolsService::isInstalled() const 0248 { 0249 return d->isInstalled; 0250 } 0251 0252 KService::Ptr KMoreToolsService::installedService() const 0253 { 0254 return d->installedService; 0255 } 0256 0257 KService::Ptr KMoreToolsService::kmtProvidedService() const 0258 { 0259 return d->kmtDesktopfile; 0260 } 0261 0262 QIcon KMoreToolsService::kmtProvidedIcon() const 0263 { 0264 return d->getKmtProvidedIcon(); 0265 } 0266 0267 QUrl KMoreToolsService::homepageUrl() const 0268 { 0269 return d->homepageUrl; 0270 } 0271 0272 void KMoreToolsService::setHomepageUrl(const QUrl &url) 0273 { 0274 d->homepageUrl = url; 0275 } 0276 0277 int KMoreToolsService::maxUrlArgCount() const 0278 { 0279 return d->maxUrlArgCount; 0280 } 0281 0282 void KMoreToolsService::setMaxUrlArgCount(int maxUrlArgCount) 0283 { 0284 d->maxUrlArgCount = maxUrlArgCount; 0285 } 0286 0287 QString KMoreToolsService::formatString(const QString &formatString) const 0288 { 0289 QString result = formatString; 0290 0291 QString genericName = d->getServiceGenericName(); 0292 if (genericName.isEmpty()) { 0293 genericName = d->getServiceName(); 0294 if (genericName.isEmpty()) { 0295 genericName = desktopEntryName(); 0296 } 0297 } 0298 0299 QString name = d->getServiceName(); 0300 if (name.isEmpty()) { 0301 name = desktopEntryName(); 0302 } 0303 0304 result.replace(QLatin1String("$GenericName"), genericName); 0305 result.replace(QLatin1String("$Name"), name); 0306 result.replace(QLatin1String("$DesktopEntryName"), desktopEntryName()); 0307 0308 return result; 0309 } 0310 0311 QIcon KMoreToolsService::icon() const 0312 { 0313 if (installedService() != nullptr) { 0314 return QIcon::fromTheme(installedService()->icon()); 0315 } else if (kmtProvidedService() != nullptr) { 0316 return d->getKmtProvidedIcon(); 0317 } else { 0318 return QIcon(); 0319 } 0320 } 0321 0322 void KMoreToolsService::setExec(const QString &exec) 0323 { 0324 auto service = installedService(); 0325 if (service) { 0326 service->setExec(exec); 0327 } 0328 } 0329 0330 QString KMoreToolsService::appstreamId() const 0331 { 0332 return d->appstreamId; 0333 } 0334 0335 void KMoreToolsService::setAppstreamId(const QString &id) 0336 { 0337 d->appstreamId = id; 0338 } 0339 0340 // ------------------------------------------------------------------------------------------------ 0341 // ------------------------------------------------------------------------------------------------ 0342 0343 const QString configFile = QStringLiteral("kmoretoolsrc"); 0344 const QString configKey = QStringLiteral("menu_structure"); 0345 0346 class KMoreToolsMenuBuilderPrivate 0347 { 0348 public: 0349 QString uniqueId; 0350 /** 0351 * default value is "", see KMoreTools::menuBuilder() 0352 */ 0353 QString userConfigPostfix; 0354 QList<KMoreToolsMenuItem *> menuItems; 0355 KmtMenuItemIdGen menuItemIdGen; 0356 QString initialItemTextTemplate = QStringLiteral("$GenericName"); 0357 0358 public: 0359 KMoreToolsMenuBuilderPrivate() 0360 { 0361 } 0362 0363 ~KMoreToolsMenuBuilderPrivate() 0364 { 0365 } 0366 0367 void deleteAndClearMenuItems() 0368 { 0369 for (auto item : std::as_const(menuItems)) { 0370 delete item; 0371 } 0372 0373 menuItems.clear(); 0374 } 0375 0376 KmtMenuStructureDto readUserConfig() const 0377 { 0378 KConfig config(configFile, KConfig::NoGlobals, QStandardPaths::ConfigLocation); 0379 auto configGroup = config.group(uniqueId + userConfigPostfix); 0380 QString json = configGroup.readEntry(configKey); 0381 KmtMenuStructureDto configuredStructure; 0382 configuredStructure.deserialize(json); 0383 return configuredStructure; 0384 } 0385 0386 void writeUserConfig(const KmtMenuStructureDto &mstruct) const 0387 { 0388 KConfig config(configFile, KConfig::NoGlobals, QStandardPaths::ConfigLocation); 0389 auto configGroup = config.group(uniqueId + userConfigPostfix); 0390 auto configValue = mstruct.serialize(); 0391 configGroup.writeEntry(configKey, configValue); 0392 configGroup.sync(); 0393 } 0394 0395 enum CreateMenuStructureOption { 0396 CreateMenuStructure_Default, 0397 CreateMenuStructure_MergeWithUserConfig, 0398 }; 0399 0400 /** 0401 * Merge strategy if createMenuStructureOption == CreateMenuStructure_MergeWithUserConfig 0402 * -------------------------------------------------------------------------------------- 0403 * 1) For each 'main' section item from configStruct 0404 * lookup in current structure (all installed items) and if found add to new structure 0405 * This means items which are in configStruct but not in current structure will be discarded. 0406 * 0407 * 2) Add remaining 'main' section items from current to new structure 0408 * 0409 * 3) Do the 1) and 2) analogous for 'more' section 0410 * 0411 * 0412 * How default structure and DTOs play together 0413 * -------------------------------------------- 0414 * Part 1: 0415 * 0416 * defaultStruct (in memory, defined by application that uses KMoreTools) 0417 * + configuredStruct (DTO, loaded from disk, from json) 0418 * = currentStruct (in memory, used to create the actual menu) 0419 * This is done by KMoreToolsMenuBuilderPrivate::createMenuStructure(mergeWithUserConfig = true). 0420 * 0421 * Part 2: 0422 * defaultStruct => defaultStructDto 0423 * currentStruct => currentStructDto 0424 * Both DTOs go to the Configure dialog. 0425 * Users edits structure => new configuredStruct (DTO => to json => to disk) 0426 * 0427 * 0428 * If createMenuStructureOption == CreateMenuStructure_Default then the default menu structure is returned. 0429 */ 0430 KmtMenuStructure createMenuStructure(CreateMenuStructureOption createMenuStructureOption) const 0431 { 0432 KmtMenuStructureDto configuredStructure; // if this stays empty then the default structure will not be changed 0433 if (createMenuStructureOption == CreateMenuStructure_MergeWithUserConfig) { 0434 // fill if should be merged 0435 configuredStructure = readUserConfig(); 0436 } 0437 0438 KmtMenuStructure mstruct; 0439 0440 QList<KMoreToolsMenuItem *> menuItemsSource = menuItems; 0441 QList<KMoreToolsMenuItem *> menuItemsSortedAsConfigured; 0442 0443 // presort as in configuredStructure 0444 for (const KmtMenuItemDto &item : std::as_const(configuredStructure.list)) { 0445 auto foundItem = std::find_if(menuItemsSource.begin(), menuItemsSource.end(), [&item](const KMoreToolsMenuItem *kMenuItem) { 0446 return kMenuItem->id() == item.id; 0447 }); 0448 if (foundItem != menuItemsSource.end()) { 0449 menuItemsSortedAsConfigured.append(*foundItem); // add to final list 0450 menuItemsSource.removeOne(*foundItem); // remove from source 0451 } 0452 } 0453 // Add remaining items from source. These may be main and more section items 0454 // so that the resulting list may have [ main items, more items, main items, more items ] 0455 // instead of only [ main items, more items ] 0456 // But in the next step this won't matter. 0457 menuItemsSortedAsConfigured.append(menuItemsSource); 0458 0459 // build MenuStructure from presorted list 0460 for (auto item : std::as_const(menuItemsSortedAsConfigured)) { 0461 const auto registeredService = item->registeredService(); 0462 0463 if ((registeredService && registeredService->isInstalled()) || !registeredService) { // if a QAction was registered directly 0464 std::optional<KmtMenuItemDto> confItem = configuredStructure.findInstalled(item->id()); 0465 if ((!confItem && item->defaultLocation() == KMoreTools::MenuSection_Main) 0466 || (confItem && confItem->menuSection == KMoreTools::MenuSection_Main)) { 0467 mstruct.mainItems.append(item); 0468 } else if ((!confItem && item->defaultLocation() == KMoreTools::MenuSection_More) 0469 || (confItem && confItem->menuSection == KMoreTools::MenuSection_More)) { 0470 mstruct.moreItems.append(item); 0471 } else { 0472 Q_ASSERT_X(false, 0473 "buildAndAppendToMenu", 0474 "invalid enum"); // todo/later: apart from static programming error, if the config garbage this might happen 0475 } 0476 } else { 0477 if (!mstruct.notInstalledServices.contains(item->registeredService())) { 0478 mstruct.notInstalledServices.append(item->registeredService()); 0479 } 0480 } 0481 } 0482 0483 return mstruct; 0484 } 0485 0486 /** 0487 * @param defaultStructure also contains the currently not-installed items 0488 */ 0489 void showConfigDialog(KmtMenuStructureDto defaultStructureDto, const QString &title = QString()) const 0490 { 0491 // read from config 0492 auto currentStructure = createMenuStructure(CreateMenuStructure_MergeWithUserConfig); 0493 auto currentStructureDto = currentStructure.toDto(); 0494 0495 KMoreToolsConfigDialog *dlg = new KMoreToolsConfigDialog(defaultStructureDto, currentStructureDto, title); 0496 if (dlg->exec() == QDialog::Accepted) { 0497 currentStructureDto = dlg->currentStructure(); 0498 writeUserConfig(currentStructureDto); 0499 } 0500 0501 delete dlg; 0502 } 0503 0504 /** 0505 * Create the 'More' menu with parent as parent 0506 * @param parent The parent of the menu 0507 */ 0508 void createMoreMenu(const KmtMenuStructure &mstruct, QMenu *parent) 0509 { 0510 for (auto item : std::as_const(mstruct.moreItems)) { 0511 const auto action = item->action(); 0512 action->setParent(parent); 0513 parent->addAction(action); 0514 } 0515 0516 if (!mstruct.notInstalledServices.isEmpty()) { 0517 parent->addSection(i18nc("@action:inmenu", "Not installed:")); 0518 0519 for (auto registeredService : std::as_const(mstruct.notInstalledServices)) { 0520 QMenu *submenuForNotInstalled = KmtNotInstalledUtil::createSubmenuForNotInstalledApp(registeredService->formatString(QStringLiteral("$Name")), 0521 parent, 0522 registeredService->icon(), 0523 registeredService->homepageUrl(), 0524 registeredService->appstreamId()); 0525 parent->addMenu(submenuForNotInstalled); 0526 } 0527 } 0528 } 0529 }; 0530 0531 KMoreToolsMenuBuilder::KMoreToolsMenuBuilder() 0532 { 0533 Q_UNREACHABLE(); 0534 } 0535 0536 KMoreToolsMenuBuilder::KMoreToolsMenuBuilder(const QString &uniqueId, const QString &userConfigPostfix) 0537 : d(new KMoreToolsMenuBuilderPrivate()) 0538 { 0539 d->uniqueId = uniqueId; 0540 d->userConfigPostfix = userConfigPostfix; 0541 } 0542 0543 KMoreToolsMenuBuilder::~KMoreToolsMenuBuilder() 0544 { 0545 d->deleteAndClearMenuItems(); 0546 } 0547 0548 void KMoreToolsMenuBuilder::setInitialItemTextTemplate(const QString &templateText) 0549 { 0550 d->initialItemTextTemplate = templateText; 0551 } 0552 0553 KMoreToolsMenuItem *KMoreToolsMenuBuilder::addMenuItem(KMoreToolsService *registeredService, KMoreTools::MenuSection defaultLocation) 0554 { 0555 auto kmtMenuItem = new KMoreToolsMenuItem(registeredService, defaultLocation, d->initialItemTextTemplate); 0556 kmtMenuItem->setId(d->menuItemIdGen.getId(registeredService->desktopEntryName())); 0557 d->menuItems.append(kmtMenuItem); 0558 return kmtMenuItem; 0559 } 0560 0561 KMoreToolsMenuItem *KMoreToolsMenuBuilder::addMenuItem(QAction *action, const QString &itemId, KMoreTools::MenuSection defaultLocation) 0562 { 0563 auto kmtMenuItem = new KMoreToolsMenuItem(action, d->menuItemIdGen.getId(itemId), defaultLocation); 0564 d->menuItems.append(kmtMenuItem); 0565 return kmtMenuItem; 0566 } 0567 0568 void KMoreToolsMenuBuilder::clear() 0569 { 0570 d->deleteAndClearMenuItems(); 0571 d->menuItemIdGen.reset(); 0572 } 0573 0574 QString KMoreToolsMenuBuilder::menuStructureAsString(bool mergeWithUserConfig) const 0575 { 0576 KmtMenuStructure mstruct = d->createMenuStructure(mergeWithUserConfig ? KMoreToolsMenuBuilderPrivate::CreateMenuStructure_MergeWithUserConfig 0577 : KMoreToolsMenuBuilderPrivate::CreateMenuStructure_Default); 0578 QString s; 0579 s += QLatin1String("|main|:"); 0580 for (auto item : std::as_const(mstruct.mainItems)) { 0581 s += item->registeredService()->desktopEntryName() + QLatin1Char('.'); 0582 } 0583 s += QLatin1String("|more|:"); 0584 for (auto item : std::as_const(mstruct.moreItems)) { 0585 s += item->registeredService()->desktopEntryName() + QLatin1Char('.'); 0586 } 0587 s += QLatin1String("|notinstalled|:"); 0588 for (auto regService : std::as_const(mstruct.notInstalledServices)) { 0589 s += regService->desktopEntryName() + QLatin1Char('.'); 0590 } 0591 return s; 0592 } 0593 0594 // TMP / for unit test 0595 void KMoreToolsMenuBuilder::showConfigDialog(const QString &title) 0596 { 0597 d->showConfigDialog(d->createMenuStructure(KMoreToolsMenuBuilderPrivate::CreateMenuStructure_Default).toDto(), title); 0598 } 0599 0600 void KMoreToolsMenuBuilder::buildByAppendingToMenu(QMenu *menu, 0601 KMoreTools::ConfigureDialogAccessibleSetting configureDialogAccessibleSetting, 0602 QMenu **outMoreMenu) 0603 { 0604 KmtMenuStructure mstruct = d->createMenuStructure(KMoreToolsMenuBuilderPrivate::CreateMenuStructure_MergeWithUserConfig); 0605 0606 for (auto item : std::as_const(mstruct.mainItems)) { 0607 const auto action = item->action(); 0608 if (!action->parent()) { // if the action has no parent, set it to the menu to be filled 0609 action->setParent(menu); 0610 } 0611 menu->addAction(action); 0612 } 0613 0614 QMenu *moreMenu = new QMenu(i18nc("@action:inmenu", "More"), menu); 0615 0616 if (!mstruct.moreItems.isEmpty() || !mstruct.notInstalledServices.isEmpty()) { 0617 if (mstruct.mainItems.isEmpty()) { 0618 d->createMoreMenu(mstruct, menu); 0619 } else { 0620 menu->addSeparator(); 0621 menu->addMenu(moreMenu); 0622 d->createMoreMenu(mstruct, moreMenu); 0623 } 0624 } 0625 0626 if (moreMenu->isEmpty()) { 0627 if (outMoreMenu) { 0628 *outMoreMenu = nullptr; 0629 } 0630 } else { 0631 if (outMoreMenu) { 0632 *outMoreMenu = moreMenu; 0633 } 0634 } 0635 0636 QMenu *baseMenu; 0637 // either the "Configure..." menu should be shown via setting or the Ctrl key is pressed 0638 if (configureDialogAccessibleSetting == KMoreTools::ConfigureDialogAccessible_Always || QApplication::keyboardModifiers() & Qt::ControlModifier 0639 || (configureDialogAccessibleSetting == KMoreTools::ConfigureDialogAccessible_Defensive && !mstruct.notInstalledServices.empty())) { 0640 if (moreMenu->isEmpty()) { // "more" menu was not created... 0641 // ...then we add the configure menu to the main menu 0642 baseMenu = menu; 0643 } else { // more menu has items 0644 // ...then it was added to main menu and has got at least on item 0645 baseMenu = moreMenu; 0646 } 0647 0648 if (!baseMenu->isEmpty()) { 0649 baseMenu->addSeparator(); 0650 auto configureAction = baseMenu->addAction(QIcon::fromTheme(QStringLiteral("configure")), i18nc("@action:inmenu", "Configure...")); 0651 configureAction->setData(QStringLiteral("configureItem")); // tag the action (currently only used in unit-test) 0652 KmtMenuStructure mstructDefault = d->createMenuStructure(KMoreToolsMenuBuilderPrivate::CreateMenuStructure_Default); 0653 KmtMenuStructureDto mstructDefaultDto = mstructDefault.toDto(); // makes sure the "Reset" button works as expected 0654 QObject::connect(configureAction, &QAction::triggered, configureAction, [this, mstructDefaultDto](bool) { 0655 this->d->showConfigDialog(mstructDefaultDto); 0656 }); 0657 } 0658 } 0659 } 0660 0661 class KMoreToolsMenuItemPrivate 0662 { 0663 public: 0664 QString id; 0665 KMoreToolsService *registeredService = nullptr; 0666 QString initialItemText; 0667 QAction *action = nullptr; 0668 KMoreTools::MenuSection defaultLocation; 0669 bool actionAutoCreated = false; // action might stay nullptr even if actionCreated is true 0670 }; 0671 0672 KMoreToolsMenuItem::KMoreToolsMenuItem(KMoreToolsService *registeredService, KMoreTools::MenuSection defaultLocation, const QString &initialItemTextTemplate) 0673 : d(new KMoreToolsMenuItemPrivate()) 0674 { 0675 d->registeredService = registeredService; 0676 d->defaultLocation = defaultLocation; 0677 0678 // set menu item caption (text) 0679 QString defaultName = registeredService->formatString(initialItemTextTemplate); // e.g. "$GenericName", "$Name" 0680 d->initialItemText = registeredService->formatString(defaultName); 0681 } 0682 0683 KMoreToolsMenuItem::KMoreToolsMenuItem(QAction *action, const QString &itemId, KMoreTools::MenuSection defaultLocation) 0684 : d(new KMoreToolsMenuItemPrivate()) 0685 { 0686 d->action = action; 0687 d->id = itemId; 0688 d->defaultLocation = defaultLocation; 0689 } 0690 0691 KMoreToolsMenuItem::~KMoreToolsMenuItem() 0692 { 0693 if (d->actionAutoCreated && d->action) { // Only do this if KMoreTools created the action. Other actions must be deleted by client. 0694 // d->action can already be nullptr in some cases. 0695 // Disconnects the 'connect' event (and potentially more; is this bad?) 0696 // that was connected in action() to detect action deletion. 0697 d->action->disconnect(d->action); 0698 } 0699 } 0700 0701 QString KMoreToolsMenuItem::id() const 0702 { 0703 return d->id; 0704 } 0705 0706 void KMoreToolsMenuItem::setId(const QString &id) 0707 { 0708 d->id = id; 0709 } 0710 0711 KMoreToolsService *KMoreToolsMenuItem::registeredService() const 0712 { 0713 return d->registeredService; 0714 } 0715 0716 KMoreTools::MenuSection KMoreToolsMenuItem::defaultLocation() const 0717 { 0718 return d->defaultLocation; 0719 } 0720 0721 QString KMoreToolsMenuItem::initialItemText() const 0722 { 0723 return d->initialItemText; 0724 } 0725 0726 void KMoreToolsMenuItem::setInitialItemText(const QString &itemText) 0727 { 0728 d->initialItemText = itemText; 0729 } 0730 0731 QAction *KMoreToolsMenuItem::action() const 0732 { 0733 // currently we assume if a registeredService is given we auto-create the QAction once 0734 if (d->registeredService && !d->actionAutoCreated) { 0735 d->actionAutoCreated = true; 0736 0737 if (d->registeredService->isInstalled()) { 0738 d->action = new QAction(d->registeredService->icon(), d->initialItemText, nullptr); 0739 // reset the action cache when action gets destroyed 0740 // this happens in unit-tests where menu.clear() is called before another buildByAppendingToMenu call 0741 // WARN: see also destructor! (might be a source of bugs?) 0742 QObject::connect(d->action, &QObject::destroyed, d->action, [this]() { 0743 this->d->actionAutoCreated = false; 0744 this->d->action = nullptr; 0745 }); 0746 } else { 0747 d->action = nullptr; 0748 } 0749 } 0750 // else: 0751 // !d->registeredService => action will be provided by user 0752 // or d->actionAutoCreated => action was autocreated (or set to nullptr if service not installed) 0753 0754 return d->action; 0755 }