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