KMoreTools

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

KDE's Doxygen guidelines are available online.