File indexing completed on 2024-12-08 12:23:16

0001 /*
0002     SPDX-FileCopyrightText: 2015 Gregor Mi <codestruct@posteo.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-or-later
0005 */
0006 
0007 #include "kmoretoolsmenufactory.h"
0008 
0009 #include "kmoretools_p.h"
0010 #include "kmoretoolspresets_p.h"
0011 #include "knewstuff_debug.h"
0012 #include <QDebug>
0013 #include <QStorageInfo>
0014 
0015 #include <KDialogJobUiDelegate>
0016 #include <KIO/ApplicationLauncherJob>
0017 #include <KLocalizedString>
0018 
0019 #include "kmoretools.h"
0020 #include "kmoretoolspresets.h"
0021 
0022 class KMoreToolsMenuFactoryPrivate
0023 {
0024 public:
0025     // Note that this object must live long enough in case the user opens
0026     // the "Configure..." dialog
0027     KMoreTools *kmt = nullptr;
0028 
0029     QMenu *menu = nullptr;
0030     QWidget *parentWidget = nullptr;
0031 };
0032 
0033 class KMoreToolsLazyMenu : public QMenu
0034 {
0035 private Q_SLOTS:
0036     void onAboutToShow()
0037     {
0038         // qDebug() << "onAboutToShow";
0039         clear();
0040         m_aboutToShowFunc(this);
0041     }
0042 
0043 public:
0044     KMoreToolsLazyMenu(QWidget *parent = nullptr)
0045         : QMenu(parent)
0046     {
0047         connect(this, &QMenu::aboutToShow, this, &KMoreToolsLazyMenu::onAboutToShow);
0048     }
0049 
0050     void setAboutToShowAction(std::function<void(QMenu *)> aboutToShowFunc)
0051     {
0052         m_aboutToShowFunc = aboutToShowFunc;
0053     }
0054 
0055 private:
0056     std::function<void(QMenu *)> m_aboutToShowFunc;
0057 };
0058 
0059 KMoreToolsMenuFactory::KMoreToolsMenuFactory(const QString &uniqueId)
0060     : d(new KMoreToolsMenuFactoryPrivate())
0061 {
0062     d->kmt = new KMoreTools(uniqueId);
0063     Q_UNUSED(m_off)
0064 }
0065 
0066 KMoreToolsMenuFactory::~KMoreToolsMenuFactory()
0067 {
0068     if (d->menu && !d->menu->parent()) {
0069         delete d->menu;
0070     }
0071 
0072     delete d->kmt;
0073 }
0074 
0075 static void runApplication(const KService::Ptr &service, const QList<QUrl> &urls)
0076 {
0077     auto *job = new KIO::ApplicationLauncherJob(service);
0078     job->setUrls(urls);
0079     job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
0080     job->start();
0081 }
0082 
0083 // "file static" => no symbol will be exported
0084 static void addItemFromKmtService(KMoreToolsMenuBuilder *menuBuilder, QMenu *menu, KMoreToolsService *kmtService, const QUrl &url, bool isMoreSection)
0085 {
0086     auto menuItem = menuBuilder->addMenuItem(kmtService, isMoreSection ? KMoreTools::MenuSection_More : KMoreTools::MenuSection_Main);
0087 
0088     if (kmtService->isInstalled()) {
0089         auto kService = kmtService->installedService();
0090 
0091         if (!kService) {
0092             // if the corresponding desktop file is not installed
0093             // then the isInstalled was true because of the Exec line check
0094             // and we use the desktopfile provided by KMoreTools.
0095             // Otherwise *kService would crash.
0096             qCDebug(KNEWSTUFF) << "Desktop file not installed:" << kmtService->desktopEntryName() << "=> Use desktop file provided by KMoreTools";
0097             kService = kmtService->kmtProvidedService();
0098         }
0099 
0100         if (!url.isEmpty() && kmtService->maxUrlArgCount() > 0) {
0101             menu->connect(menuItem->action(), &QAction::triggered, menu, [kService, url](bool) {
0102                 runApplication(kService, {url});
0103             });
0104         } else {
0105             menu->connect(menuItem->action(), &QAction::triggered, menu, [kService](bool) {
0106                 runApplication(kService, {});
0107             });
0108         }
0109     }
0110 }
0111 
0112 // "file static" => no symbol will be exported
0113 static void addItemsFromKmtServiceList(KMoreToolsMenuBuilder *menuBuilder,
0114                                        QMenu *menu,
0115                                        const QList<KMoreToolsService *> &kmtServiceList,
0116                                        const QUrl &url,
0117                                        bool isMoreSection,
0118                                        QString firstMoreSectionDesktopEntryName)
0119 {
0120     for (auto kmtService : kmtServiceList) {
0121         // Check the pointer just in case a null pointer got in somewhere
0122         if (!kmtService) {
0123             continue;
0124         }
0125         if (kmtService->desktopEntryName() == firstMoreSectionDesktopEntryName) {
0126             // once we reach the potential first "more section desktop entry name"
0127             // all remaining services are added to the more section by default
0128             isMoreSection = true;
0129         }
0130         addItemFromKmtService(menuBuilder, menu, kmtService, url, isMoreSection);
0131     }
0132 }
0133 
0134 /**
0135  * "file static" => no symbol will be exported
0136  * @param isMoreSection: true => all items will be added into the more section
0137  * @param firstMoreSectionDesktopEntryName: only valid when @p isMoreSection is false:
0138  *                                           see KMoreToolsPresets::registerServicesByGroupingNames
0139  */
0140 static void addItemsForGroupingNameWithSpecialHandling(KMoreToolsMenuBuilder *menuBuilder,
0141                                                        QMenu *menu,
0142                                                        QList<KMoreToolsService *> kmtServiceList,
0143                                                        const QString &groupingName,
0144                                                        const QUrl &url,
0145                                                        bool isMoreSection,
0146                                                        QString firstMoreSectionDesktopEntryName)
0147 {
0148     //
0149     // special handlings
0150     //
0151     if (groupingName == QLatin1String("disk-usage") && !url.isEmpty()) {
0152         //
0153         // "disk-usage" plus a given URL. If no url is given there is no need
0154         // for special handling
0155         //
0156 
0157         auto filelightAppIter = std::find_if(kmtServiceList.begin(), kmtServiceList.end(), [](KMoreToolsService *s) {
0158             return s->desktopEntryName() == QLatin1String("org.kde.filelight");
0159         });
0160 
0161         if (filelightAppIter != kmtServiceList.end()) {
0162             auto filelightApp = *filelightAppIter;
0163 
0164             // because we later add all remaining items
0165             kmtServiceList.removeOne(filelightApp);
0166 
0167                 const auto filelight1Item = menuBuilder->addMenuItem(filelightApp);
0168 
0169                 if (filelightApp->isInstalled()) {
0170                     const auto filelightService = filelightApp->installedService();
0171 
0172                     filelight1Item->action()->setText(
0173                         filelightApp->formatString(i18nc("@action:inmenu %1=\"$GenericName\"", "%1 - current folder", QStringLiteral("$GenericName"))));
0174                     menu->connect(filelight1Item->action(), &QAction::triggered, menu, [filelightService, url](bool) {
0175                         runApplication(filelightService, {url});
0176                     });
0177 
0178                     // For remote URLs like FTP analyzing the device makes no sense
0179                     if (url.isLocalFile()) {
0180                         const auto filelight2Item = menuBuilder->addMenuItem(filelightApp);
0181                         filelight2Item->action()->setText(
0182                             filelightApp->formatString(i18nc("@action:inmenu %1=\"$GenericName\"", "%1 - current device", QStringLiteral("$GenericName"))));
0183                         menu->connect(filelight2Item->action(), &QAction::triggered, menu, [filelightService, url](bool) {
0184                             const QStorageInfo info(url.toLocalFile());
0185 
0186                             if (info.isValid() && info.isReady()) {
0187                                 runApplication(filelightService, {QUrl::fromLocalFile(info.rootPath())});
0188                             }
0189                         });
0190                     }
0191 
0192                     auto filelight3Item = menuBuilder->addMenuItem(filelightApp, KMoreTools::MenuSection_More);
0193                     if (filelightApp->isInstalled()) {
0194                         filelight3Item->action()->setText(
0195                             filelightApp->formatString(i18nc("@action:inmenu %1=\"$GenericName\"", "%1 - all devices", QStringLiteral("$GenericName"))));
0196                         const auto filelightService = filelightApp->installedService();
0197                         menu->connect(filelight3Item->action(), &QAction::triggered, menu, [filelightService](bool) {
0198                             runApplication(filelightService, {});
0199                         });
0200                     }
0201                 }
0202         } else {
0203             qWarning() << "org.kde.filelight should be present in KMoreTools but it is not!";
0204         }
0205 
0206     } else if (groupingName == QLatin1String("disk-partitions")) {
0207         // better because the Partition editors all have the same GenericName
0208         menuBuilder->setInitialItemTextTemplate(QStringLiteral("$GenericName ($Name)"));
0209 
0210         addItemsFromKmtServiceList(menuBuilder, menu, kmtServiceList, url, isMoreSection, firstMoreSectionDesktopEntryName);
0211 
0212         menuBuilder->setInitialItemTextTemplate(QStringLiteral("$GenericName")); // set back to default
0213 
0214         return; // skip processing remaining list (would result in duplicates)
0215 
0216     } else if (groupingName == QLatin1String("git-clients-and-actions")) {
0217         // Here we change the default item text and make sure that the url
0218         // argument is properly handled.
0219         //
0220 
0221         menuBuilder->setInitialItemTextTemplate(QStringLiteral("$Name")); // just use the application name
0222 
0223         for (auto kmtService : std::as_const(kmtServiceList)) {
0224             // Check the pointer just in case a null pointer got in somewhere
0225             if (!kmtService) {
0226                 continue;
0227             }
0228             QUrl argUrl = url;
0229 
0230             if (url.isLocalFile()) { // this can only be done for local files, remote urls probably won't work for git clients anyway
0231                 // by default we need an URL pointing to a directory
0232                 // (this impl currently leads to wrong behaviour if the root dir of a git repo is chosen because it always goes one level up)
0233                 argUrl = KmtUrlUtil::localFileAbsoluteDir(url); // needs local file
0234 
0235                 if (kmtService->desktopEntryName() == _("git-cola-view-history.kmt-edition")) {
0236                     // in this case we need the file because we would like to see its history
0237                     argUrl = url;
0238                 }
0239             }
0240 
0241             addItemFromKmtService(menuBuilder, menu, kmtService, argUrl, isMoreSection);
0242         }
0243 
0244         menuBuilder->setInitialItemTextTemplate(QStringLiteral("$GenericName")); // set back to default
0245 
0246         return; // skip processing remaining list (would result in duplicates)
0247     }
0248 
0249     //
0250     // default handling (or process remaining list)
0251     //
0252     menuBuilder->setInitialItemTextTemplate(QStringLiteral("$Name")); // just use the application name
0253     addItemsFromKmtServiceList(menuBuilder, menu, kmtServiceList, url, isMoreSection, firstMoreSectionDesktopEntryName);
0254     menuBuilder->setInitialItemTextTemplate(QStringLiteral("$GenericName")); // set back to default
0255 }
0256 
0257 QMenu *KMoreToolsMenuFactory::createMenuFromGroupingNames(const QStringList &groupingNames, const QUrl &url)
0258 {
0259     delete d->menu;
0260 
0261     auto menu = new KMoreToolsLazyMenu(d->parentWidget);
0262     menu->setAboutToShowAction([this, groupingNames, url](QMenu *m) {
0263         fillMenuFromGroupingNames(m, groupingNames, url);
0264     });
0265     d->menu = menu;
0266 
0267     return d->menu;
0268 }
0269 
0270 void KMoreToolsMenuFactory::fillMenuFromGroupingNames(QMenu *menu, const QStringList &groupingNames, const QUrl &url)
0271 {
0272     const auto menuBuilder = d->kmt->menuBuilder();
0273     menuBuilder->clear();
0274 
0275     bool isMoreSection = false;
0276 
0277     for (const auto &groupingName : groupingNames) {
0278         if (groupingName == QLatin1String("more:")) {
0279             isMoreSection = true;
0280             continue;
0281         }
0282 
0283         QString firstMoreSectionDesktopEntryName;
0284         auto kmtServiceList = KMoreToolsPresetsPrivate::registerServicesByGroupingNames(&firstMoreSectionDesktopEntryName, d->kmt, {groupingName});
0285 
0286         addItemsForGroupingNameWithSpecialHandling(menuBuilder, menu, kmtServiceList, groupingName, url, isMoreSection, firstMoreSectionDesktopEntryName);
0287     }
0288 
0289     menuBuilder->buildByAppendingToMenu(menu);
0290 }
0291 
0292 void KMoreToolsMenuFactory::setParentWidget(QWidget *widget)
0293 {
0294     d->parentWidget = widget;
0295 }