1 /*
2 SPDX-FileCopyrightText: 2015 Gregor Mi <codestruct@posteo.org>
3
4 SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6
7 #include "kmoretoolsmenufactory.h"
8
9 #include "kmoretools_p.h"
10 #include "kmoretoolspresets_p.h"
11 #include "knewstuff_debug.h"
12 #include <QDebug>
13 #include <QStorageInfo>
14
15 #include <KDialogJobUiDelegate>
16 #include <KIO/ApplicationLauncherJob>
17 #include <KLocalizedString>
18 #include <KNS3/KMoreTools>
19 #include <KNS3/KMoreToolsPresets>
20
21 class KMoreToolsMenuFactoryPrivate
22 {
23 public:
24 // Note that this object must live long enough in case the user opens
25 // the "Configure..." dialog
26 KMoreTools *kmt = nullptr;
27
28 QMenu *menu = nullptr;
29 QWidget *parentWidget = nullptr;
30 };
31
32 class KMoreToolsLazyMenu : public QMenu
33 {
34 private Q_SLOTS:
onAboutToShow()35 void onAboutToShow()
36 {
37 // qDebug() << "onAboutToShow";
38 clear();
39 m_aboutToShowFunc(this);
40 }
41
42 public:
KMoreToolsLazyMenu(QWidget * parent=nullptr)43 KMoreToolsLazyMenu(QWidget *parent = nullptr)
44 : QMenu(parent)
45 {
46 connect(this, &QMenu::aboutToShow, this, &KMoreToolsLazyMenu::onAboutToShow);
47 }
48
setAboutToShowAction(std::function<void (QMenu *)> aboutToShowFunc)49 void setAboutToShowAction(std::function<void(QMenu *)> aboutToShowFunc)
50 {
51 m_aboutToShowFunc = aboutToShowFunc;
52 }
53
54 private:
55 std::function<void(QMenu *)> m_aboutToShowFunc;
56 };
57
KMoreToolsMenuFactory(const QString & uniqueId)58 KMoreToolsMenuFactory::KMoreToolsMenuFactory(const QString &uniqueId)
59 : d(new KMoreToolsMenuFactoryPrivate())
60 {
61 d->kmt = new KMoreTools(uniqueId);
62 Q_UNUSED(m_off)
63 }
64
~KMoreToolsMenuFactory()65 KMoreToolsMenuFactory::~KMoreToolsMenuFactory()
66 {
67 if (d->menu && !d->menu->parent()) {
68 delete d->menu;
69 }
70
71 delete d->kmt;
72
73 delete d;
74 }
75
runApplication(const KService::Ptr & service,const QList<QUrl> & urls)76 static void runApplication(const KService::Ptr &service, const QList<QUrl> &urls)
77 {
78 auto *job = new KIO::ApplicationLauncherJob(service);
79 job->setUrls(urls);
80 job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, nullptr));
81 job->start();
82 }
83
84 // "file static" => no symbol will be exported
addItemFromKmtService(KMoreToolsMenuBuilder * menuBuilder,QMenu * menu,KMoreToolsService * kmtService,const QUrl & url,bool isMoreSection)85 static void addItemFromKmtService(KMoreToolsMenuBuilder *menuBuilder, QMenu *menu, KMoreToolsService *kmtService, const QUrl &url, bool isMoreSection)
86 {
87 auto menuItem = menuBuilder->addMenuItem(kmtService, isMoreSection ? KMoreTools::MenuSection_More : KMoreTools::MenuSection_Main);
88
89 if (kmtService->isInstalled()) {
90 auto kService = kmtService->installedService();
91
92 if (!kService) {
93 // if the corresponding desktop file is not installed
94 // then the isInstalled was true because of the Exec line check
95 // and we use the desktopfile provided by KMoreTools.
96 // Otherwise *kService would crash.
97 qCDebug(KNEWSTUFF) << "Desktop file not installed:" << kmtService->desktopEntryName() << "=> Use desktop file provided by KMoreTools";
98 kService = kmtService->kmtProvidedService();
99 }
100
101 if (!url.isEmpty() && kmtService->maxUrlArgCount() > 0) {
102 menu->connect(menuItem->action(), &QAction::triggered, menu, [kService, url](bool) {
103 runApplication(kService, {url});
104 });
105 } else {
106 menu->connect(menuItem->action(), &QAction::triggered, menu, [kService](bool) {
107 runApplication(kService, {});
108 });
109 }
110 }
111 }
112
113 // "file static" => no symbol will be exported
addItemsFromKmtServiceList(KMoreToolsMenuBuilder * menuBuilder,QMenu * menu,const QList<KMoreToolsService * > & kmtServiceList,const QUrl & url,bool isMoreSection,QString firstMoreSectionDesktopEntryName)114 static void addItemsFromKmtServiceList(KMoreToolsMenuBuilder *menuBuilder,
115 QMenu *menu,
116 const QList<KMoreToolsService *> &kmtServiceList,
117 const QUrl &url,
118 bool isMoreSection,
119 QString firstMoreSectionDesktopEntryName)
120 {
121 for (auto kmtService : kmtServiceList) {
122 // Check the pointer just in case a null pointer got in somewhere
123 if (!kmtService) {
124 continue;
125 }
126 if (kmtService->desktopEntryName() == firstMoreSectionDesktopEntryName) {
127 // once we reach the potential first "more section desktop entry name"
128 // all remaining services are added to the more section by default
129 isMoreSection = true;
130 }
131 addItemFromKmtService(menuBuilder, menu, kmtService, url, isMoreSection);
132 }
133 }
134
135 /**
136 * "file static" => no symbol will be exported
137 * @param isMoreSection: true => all items will be added into the more section
138 * @param firstMoreSectionDesktopEntryName: only valid when @p isMoreSection is false:
139 * see KMoreToolsPresets::registerServicesByGroupingNames
140 */
addItemsForGroupingNameWithSpecialHandling(KMoreToolsMenuBuilder * menuBuilder,QMenu * menu,QList<KMoreToolsService * > kmtServiceList,const QString & groupingName,const QUrl & url,bool isMoreSection,QString firstMoreSectionDesktopEntryName)141 static void addItemsForGroupingNameWithSpecialHandling(KMoreToolsMenuBuilder *menuBuilder,
142 QMenu *menu,
143 QList<KMoreToolsService *> kmtServiceList,
144 const QString &groupingName,
145 const QUrl &url,
146 bool isMoreSection,
147 QString firstMoreSectionDesktopEntryName)
148 {
149 //
150 // special handlings
151 //
152 if (groupingName == QLatin1String("disk-usage") && !url.isEmpty()) {
153 //
154 // "disk-usage" plus a given URL. If no url is given there is no need
155 // for special handling
156 //
157
158 auto filelightAppIter = std::find_if(kmtServiceList.begin(), kmtServiceList.end(), [](KMoreToolsService *s) {
159 return s->desktopEntryName() == QLatin1String("org.kde.filelight");
160 });
161
162 if (filelightAppIter != kmtServiceList.end()) {
163 auto filelightApp = *filelightAppIter;
164
165 // because we later add all remaining items
166 kmtServiceList.removeOne(filelightApp);
167
168 if (url.isLocalFile()) { // 2015-01-12: Filelight can handle FTP connections
169 // but KIO/kioexec cannot (bug or feature?), so we
170 // don't offer it in this case
171
172 const auto filelight1Item = menuBuilder->addMenuItem(filelightApp);
173
174 if (filelightApp->isInstalled()) {
175 const auto filelightService = filelightApp->installedService();
176
177 filelight1Item->action()->setText(
178 filelightApp->formatString(i18nc("@action:inmenu %1=\"$GenericName\"", "%1 - current folder", QStringLiteral("$GenericName"))));
179 menu->connect(filelight1Item->action(), &QAction::triggered, menu, [filelightService, url](bool) {
180 runApplication(filelightService, {url});
181 });
182
183 const auto filelight2Item = menuBuilder->addMenuItem(filelightApp);
184 filelight2Item->action()->setText(
185 filelightApp->formatString(i18nc("@action:inmenu %1=\"$GenericName\"", "%1 - current device", QStringLiteral("$GenericName"))));
186 menu->connect(filelight2Item->action(), &QAction::triggered, menu, [filelightService, url](bool) {
187 const QStorageInfo info(url.toLocalFile());
188
189 if (info.isValid() && info.isReady()) {
190 runApplication(filelightService, {QUrl::fromLocalFile(info.rootPath())});
191 }
192 });
193 }
194 }
195
196 auto filelight3Item = menuBuilder->addMenuItem(filelightApp, KMoreTools::MenuSection_More);
197 if (filelightApp->isInstalled()) {
198 filelight3Item->action()->setText(
199 filelightApp->formatString(i18nc("@action:inmenu %1=\"$GenericName\"", "%1 - all devices", QStringLiteral("$GenericName"))));
200 const auto filelightService = filelightApp->installedService();
201 menu->connect(filelight3Item->action(), &QAction::triggered, menu, [filelightService](bool) {
202 runApplication(filelightService, {});
203 });
204 }
205 } else {
206 qWarning() << "org.kde.filelight should be present in KMoreTools but it is not!";
207 }
208
209 } else if (groupingName == QLatin1String("disk-partitions")) {
210 // better because the Partition editors all have the same GenericName
211 menuBuilder->setInitialItemTextTemplate(QStringLiteral("$GenericName ($Name)"));
212
213 addItemsFromKmtServiceList(menuBuilder, menu, kmtServiceList, url, isMoreSection, firstMoreSectionDesktopEntryName);
214
215 menuBuilder->setInitialItemTextTemplate(QStringLiteral("$GenericName")); // set back to default
216
217 return; // skip processing remaining list (would result in duplicates)
218
219 } else if (groupingName == QLatin1String("git-clients-and-actions")) {
220 // Here we change the default item text and make sure that the url
221 // argument is properly handled.
222 //
223
224 menuBuilder->setInitialItemTextTemplate(QStringLiteral("$Name")); // just use the application name
225
226 for (auto kmtService : std::as_const(kmtServiceList)) {
227 // Check the pointer just in case a null pointer got in somewhere
228 if (!kmtService) {
229 continue;
230 }
231 QUrl argUrl = url;
232
233 if (url.isLocalFile()) { // this can only be done for local files, remote urls probably won't work for git clients anyway
234 // by default we need an URL pointing to a directory
235 // (this impl currently leads to wrong behaviour if the root dir of a git repo is chosen because it always goes one level up)
236 argUrl = KmtUrlUtil::localFileAbsoluteDir(url); // needs local file
237
238 if (kmtService->desktopEntryName() == _("git-cola-view-history.kmt-edition")) {
239 // in this case we need the file because we would like to see its history
240 argUrl = url;
241 }
242 }
243
244 addItemFromKmtService(menuBuilder, menu, kmtService, argUrl, isMoreSection);
245 }
246
247 menuBuilder->setInitialItemTextTemplate(QStringLiteral("$GenericName")); // set back to default
248
249 return; // skip processing remaining list (would result in duplicates)
250 }
251
252 //
253 // default handling (or process remaining list)
254 //
255 menuBuilder->setInitialItemTextTemplate(QStringLiteral("$Name")); // just use the application name
256 addItemsFromKmtServiceList(menuBuilder, menu, kmtServiceList, url, isMoreSection, firstMoreSectionDesktopEntryName);
257 menuBuilder->setInitialItemTextTemplate(QStringLiteral("$GenericName")); // set back to default
258 }
259
createMenuFromGroupingNames(const QStringList & groupingNames,const QUrl & url)260 QMenu *KMoreToolsMenuFactory::createMenuFromGroupingNames(const QStringList &groupingNames, const QUrl &url)
261 {
262 delete d->menu;
263
264 auto menu = new KMoreToolsLazyMenu(d->parentWidget);
265 menu->setAboutToShowAction([this, groupingNames, url](QMenu *m) {
266 fillMenuFromGroupingNames(m, groupingNames, url);
267 });
268 d->menu = menu;
269
270 return d->menu;
271 }
272
fillMenuFromGroupingNames(QMenu * menu,const QStringList & groupingNames,const QUrl & url)273 void KMoreToolsMenuFactory::fillMenuFromGroupingNames(QMenu *menu, const QStringList &groupingNames, const QUrl &url)
274 {
275 const auto menuBuilder = d->kmt->menuBuilder();
276 menuBuilder->clear();
277
278 bool isMoreSection = false;
279
280 for (const auto &groupingName : groupingNames) {
281 if (groupingName == QLatin1String("more:")) {
282 isMoreSection = true;
283 continue;
284 }
285
286 QString firstMoreSectionDesktopEntryName;
287 auto kmtServiceList = KMoreToolsPresetsPrivate::registerServicesByGroupingNames(&firstMoreSectionDesktopEntryName, d->kmt, {groupingName});
288
289 addItemsForGroupingNameWithSpecialHandling(menuBuilder, menu, kmtServiceList, groupingName, url, isMoreSection, firstMoreSectionDesktopEntryName);
290 }
291
292 menuBuilder->buildByAppendingToMenu(menu);
293 }
294
setParentWidget(QWidget * widget)295 void KMoreToolsMenuFactory::setParentWidget(QWidget *widget)
296 {
297 d->parentWidget = widget;
298 }
299