KService

vfolder_menu.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "kbuildsycocainterface_p.h"
9#include "kservicefactory_p.h"
10#include "sycocadebug.h"
11#include "vfolder_menu_p.h"
12
13#include <kservice.h>
14
15#include <QDebug>
16#include <QDir>
17#include <QDirIterator>
18#include <QFile>
19#include <QMap>
20#include <QStandardPaths>
21
22static void foldNode(QDomElement &docElem, QDomElement &e, QMap<QString, QDomElement> &dupeList, QString s = QString()) // krazy:exclude=passbyvalue
23{
24 if (s.isEmpty()) {
25 s = e.text();
26 }
27 auto it = dupeList.find(s);
28 if (it != dupeList.end()) {
29 // qCDebug(SYCOCA) << e.tagName() << "and" << s << "requires combining!";
30
31 docElem.removeChild(*it);
32 dupeList.erase(it);
33 }
34 dupeList.insert(s, e);
35}
36
37static void replaceNode(QDomElement &docElem, QDomNode &node, const QStringList &list, const QString &tag)
38{
39 for (const QString &str : list) {
40 QDomElement element = docElem.ownerDocument().createElement(tag);
41 const QDomText txt = docElem.ownerDocument().createTextNode(str);
42 element.appendChild(txt);
43 docElem.insertAfter(element, node);
44 }
45
46 QDomNode next = node.nextSibling();
47 docElem.removeChild(node);
48 node = next;
49 // qCDebug(SYCOCA) << "Next tag = " << n.toElement().tagName();
50}
51
52void VFolderMenu::registerFile(const QString &file)
53{
54 int i = file.lastIndexOf(QLatin1Char('/'));
55 if (i < 0) {
56 return;
57 }
58
59 QString dir = file.left(i + 1); // Include trailing '/'
60 registerDirectory(dir);
61}
62
63void VFolderMenu::registerDirectory(const QString &directory)
64{
65 m_allDirectories.append(directory);
66}
67
68QStringList VFolderMenu::allDirectories()
69{
70 if (m_allDirectories.isEmpty()) {
71 return m_allDirectories;
72 }
73 m_allDirectories.sort();
74
75 QStringList::Iterator it = m_allDirectories.begin();
76 QString previous = *it++;
77 for (; it != m_allDirectories.end();) {
78#ifndef Q_OS_WIN
79 if ((*it).startsWith(previous))
80#else
81 if ((*it).startsWith(previous, Qt::CaseInsensitive))
82#endif
83 {
84 it = m_allDirectories.erase(it);
85 } else {
86 previous = *it;
87 ++it;
88 }
89 }
90 return m_allDirectories;
91}
92
93static void track(const QString &menuId,
94 const QString &menuName,
95 const QHash<QString, KService::Ptr> &includeList,
96 const QHash<QString, KService::Ptr> &excludeList,
97 const QHash<QString, KService::Ptr> &itemList,
98 const QString &comment)
99{
100 if (itemList.contains(menuId)) {
101 printf("%s: %s INCL %d EXCL %d\n",
102 qPrintable(menuName),
103 qPrintable(comment),
104 includeList.contains(menuId) ? 1 : 0,
105 excludeList.contains(menuId) ? 1 : 0);
106 }
107}
108
109void VFolderMenu::includeItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2)
110{
111 for (const KService::Ptr &p : items2) {
112 items1.insert(p->menuId(), p);
113 }
114}
115
116void VFolderMenu::matchItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2)
117{
118 const QHash<QString, KService::Ptr> tmpItems1 = items1;
119 for (const KService::Ptr &p : tmpItems1) {
120 QString id = p->menuId();
121 if (!items2.contains(id)) {
122 items1.remove(id);
123 }
124 }
125}
126
127void VFolderMenu::excludeItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2)
128{
129 for (const KService::Ptr &p : items2) {
130 items1.remove(p->menuId());
131 }
132}
133
134VFolderMenu::SubMenu *VFolderMenu::takeSubMenu(SubMenu *parentMenu, const QString &menuName)
135{
136 const int i = menuName.indexOf(QLatin1Char('/'));
137 const QString s1 = i > 0 ? menuName.left(i) : menuName;
138 const QString s2 = menuName.mid(i + 1);
139
140 // Look up menu
141 for (QList<SubMenu *>::Iterator it = parentMenu->subMenus.begin(); it != parentMenu->subMenus.end(); ++it) {
142 SubMenu *menu = *it;
143 if (menu->name == s1) {
144 if (i == -1) {
145 // Take it out
146 parentMenu->subMenus.erase(it);
147 return menu;
148 } else {
149 return takeSubMenu(menu, s2);
150 }
151 }
152 }
153 return nullptr; // Not found
154}
155
156void VFolderMenu::mergeMenu(SubMenu *menu1, SubMenu *menu2, bool reversePriority)
157{
158 if (m_track) {
159 track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QStringLiteral("Before MenuMerge w. %1 (incl)").arg(menu2->name));
160 track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QStringLiteral("Before MenuMerge w. %1 (excl)").arg(menu2->name));
161 }
162 if (reversePriority) {
163 // Merge menu1 with menu2, menu1 takes precedent
164 excludeItems(menu2->items, menu1->excludeItems);
165 includeItems(menu1->items, menu2->items);
166 excludeItems(menu2->excludeItems, menu1->items);
167 includeItems(menu1->excludeItems, menu2->excludeItems);
168 } else {
169 // Merge menu1 with menu2, menu2 takes precedent
170 excludeItems(menu1->items, menu2->excludeItems);
171 includeItems(menu1->items, menu2->items);
172 includeItems(menu1->excludeItems, menu2->excludeItems);
173 menu1->isDeleted = menu2->isDeleted;
174 }
175 while (!menu2->subMenus.isEmpty()) {
176 SubMenu *subMenu = menu2->subMenus.takeFirst();
177 insertSubMenu(menu1, subMenu->name, subMenu, reversePriority);
178 }
179
180 if (reversePriority) {
181 // Merge menu1 with menu2, menu1 takes precedent
182 if (menu1->directoryFile.isEmpty()) {
183 menu1->directoryFile = menu2->directoryFile;
184 }
185 if (menu1->defaultLayoutNode.isNull()) {
186 menu1->defaultLayoutNode = menu2->defaultLayoutNode;
187 }
188 if (menu1->layoutNode.isNull()) {
189 menu1->layoutNode = menu2->layoutNode;
190 }
191 } else {
192 // Merge menu1 with menu2, menu2 takes precedent
193 if (!menu2->directoryFile.isEmpty()) {
194 menu1->directoryFile = menu2->directoryFile;
195 }
196 if (!menu2->defaultLayoutNode.isNull()) {
197 menu1->defaultLayoutNode = menu2->defaultLayoutNode;
198 }
199 if (!menu2->layoutNode.isNull()) {
200 menu1->layoutNode = menu2->layoutNode;
201 }
202 }
203
204 if (m_track) {
205 track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QStringLiteral("After MenuMerge w. %1 (incl)").arg(menu2->name));
206 track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QStringLiteral("After MenuMerge w. %1 (excl)").arg(menu2->name));
207 }
208
209 delete menu2;
210}
211
212void VFolderMenu::insertSubMenu(SubMenu *parentMenu, const QString &menuName, SubMenu *newMenu, bool reversePriority)
213{
214 const int i = menuName.indexOf(QLatin1Char('/'));
215 const QString s1 = menuName.left(i);
216 const QString s2 = menuName.mid(i + 1);
217
218 // Look up menu
219 for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
220 if (menu->name == s1) {
221 if (i == -1) {
222 mergeMenu(menu, newMenu, reversePriority);
223 return;
224 } else {
225 insertSubMenu(menu, s2, newMenu, reversePriority);
226 return;
227 }
228 }
229 }
230 if (i == -1) {
231 // Add it here
232 newMenu->name = menuName;
233 parentMenu->subMenus.append(newMenu);
234 } else {
235 SubMenu *menu = new SubMenu;
236 menu->name = s1;
237 parentMenu->subMenus.append(menu);
238 insertSubMenu(menu, s2, newMenu);
239 }
240}
241
242void VFolderMenu::insertService(SubMenu *parentMenu, const QString &name, KService::Ptr newService)
243{
244 const int i = name.indexOf(QLatin1Char('/'));
245
246 if (i == -1) {
247 // Add it here
248 parentMenu->items.insert(newService->menuId(), newService);
249 return;
250 }
251
252 QString s1 = name.left(i);
253 QString s2 = name.mid(i + 1);
254
255 // Look up menu
256 for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
257 if (menu->name == s1) {
258 insertService(menu, s2, newService);
259 return;
260 }
261 }
262
263 SubMenu *menu = new SubMenu;
264 menu->name = s1;
265 parentMenu->subMenus.append(menu);
266 insertService(menu, s2, newService);
267}
268
269VFolderMenu::VFolderMenu(KServiceFactory *serviceFactory, KBuildSycocaInterface *kbuildsycocaInterface)
270 : m_appsInfo(nullptr)
271 , m_rootMenu(nullptr)
272 , m_currentMenu(nullptr)
273 , m_track(false)
274 , m_serviceFactory(serviceFactory)
275 , m_kbuildsycocaInterface(kbuildsycocaInterface)
276{
277 m_usedAppsDict.reserve(797);
278 initDirs();
279}
280
281VFolderMenu::~VFolderMenu()
282{
283 qDeleteAll(m_appsInfoList);
284 delete m_rootMenu;
285}
286// clang-format off
287#define FOR_ALL_APPLICATIONS(it) \
288 for (AppsInfo *info : std::as_const(m_appsInfoStack)) \
289 { \
290 QHashIterator<QString,KService::Ptr> it = info->applications; \
291 while (it.hasNext()) \
292 { \
293 it.next();
294#define FOR_ALL_APPLICATIONS_END } }
295
296#define FOR_CATEGORY(category, it) \
297 for (AppsInfo *info : std::as_const(m_appsInfoStack)) \
298 { \
299 const KService::List list = info->dictCategories.value(category); \
300 for(KService::List::ConstIterator it = list.constBegin(); \
301 it != list.constEnd(); ++it) \
302 {
303#define FOR_CATEGORY_END } }
304// clang-format on
305
306KService::Ptr VFolderMenu::findApplication(const QString &relPath)
307{
308 for (AppsInfo *info : std::as_const(m_appsInfoStack)) {
309 if (info->applications.contains(relPath)) {
310 KService::Ptr s = info->applications[relPath];
311 if (s) {
312 return s;
313 }
314 }
315 }
316 return KService::Ptr();
317}
318
319void VFolderMenu::addApplication(const QString &id, KService::Ptr service)
320{
321 service->setMenuId(id);
322 m_appsInfo->applications.insert(id, service); // replaces, if already there
323 m_serviceFactory->addEntry(KSycocaEntry::Ptr(service));
324}
325
326void VFolderMenu::buildApplicationIndex(bool unusedOnly)
327{
328 for (AppsInfo *info : std::as_const(m_appsInfoList)) {
329 info->dictCategories.clear();
330 QMutableHashIterator<QString, KService::Ptr> it = info->applications;
331 while (it.hasNext()) {
332 KService::Ptr s = it.next().value();
333 if (unusedOnly && m_usedAppsDict.contains(s->menuId())) {
334 // Remove and skip this one
335 it.remove();
336 continue;
337 }
338
339 const auto categories = s->categories();
340 for (const QString &cat : categories) {
341 info->dictCategories[cat].append(s); // find or insert entry in hash
342 }
343 }
344 }
345}
346
347void VFolderMenu::createAppsInfo()
348{
349 if (m_appsInfo) {
350 return;
351 }
352
353 m_appsInfo = new AppsInfo;
354 m_appsInfoStack.prepend(m_appsInfo);
355 m_appsInfoList.append(m_appsInfo);
356 m_currentMenu->apps_info = m_appsInfo;
357}
358
359void VFolderMenu::loadAppsInfo()
360{
361 m_appsInfo = m_currentMenu->apps_info;
362 if (!m_appsInfo) {
363 return; // No appsInfo for this menu
364 }
365
366 if (!m_appsInfoStack.isEmpty() && m_appsInfoStack.first() == m_appsInfo) {
367 return; // Already added (By createAppsInfo?)
368 }
369
370 m_appsInfoStack.prepend(m_appsInfo); // Add
371}
372
373void VFolderMenu::unloadAppsInfo()
374{
375 m_appsInfo = m_currentMenu->apps_info;
376 if (!m_appsInfo) {
377 return; // No appsInfo for this menu
378 }
379
380 if (m_appsInfoStack.first() != m_appsInfo) {
381 return; // Already removed (huh?)
382 }
383
384 m_appsInfoStack.removeAll(m_appsInfo); // Remove
385 m_appsInfo = nullptr;
386}
387
388QString VFolderMenu::absoluteDir(const QString &_dir, const QString &baseDir, bool keepRelativeToCfg)
389{
390 QString dir = _dir;
391 if (QDir::isRelativePath(dir)) {
392 dir = baseDir + dir;
393 }
394
395 bool relative = QDir::isRelativePath(dir);
396 if (relative && !keepRelativeToCfg) {
397 relative = false;
399 }
400
401 if (!relative) {
402 QString resolved = QDir(dir).canonicalPath();
403 if (!resolved.isEmpty()) {
404 dir = resolved;
405 }
406 }
407
408 if (!dir.endsWith(QLatin1Char('/'))) {
409 dir += QLatin1Char('/');
410 }
411
412 return dir;
413}
414
415static void tagBaseDir(QDomDocument &doc, const QString &tag, const QString &dir)
416{
417 QDomNodeList mergeFileList = doc.elementsByTagName(tag);
418 for (int i = 0; i < mergeFileList.count(); i++) {
419 QDomAttr attr = doc.createAttribute(QStringLiteral("__BaseDir"));
420 attr.setValue(dir);
421 mergeFileList.item(i).toElement().setAttributeNode(attr);
422 }
423}
424
425static void tagBasePath(QDomDocument &doc, const QString &tag, const QString &path)
426{
427 QDomNodeList mergeFileList = doc.elementsByTagName(tag);
428 for (int i = 0; i < mergeFileList.count(); i++) {
429 QDomAttr attr = doc.createAttribute(QStringLiteral("__BasePath"));
430 attr.setValue(path);
431 mergeFileList.item(i).toElement().setAttributeNode(attr);
432 }
433}
434
435QDomDocument VFolderMenu::loadDoc()
436{
437 QDomDocument doc;
438 if (m_docInfo.path.isEmpty()) {
439 return doc;
440 }
441 QFile file(m_docInfo.path);
442 if (!file.open(QIODevice::ReadOnly)) {
443 qCWarning(SYCOCA) << "Could not open " << m_docInfo.path;
444 return doc;
445 }
446 if (file.size() == 0) {
447 return doc;
448 }
449 const auto result = doc.setContent(&file);
450 if (!result) {
451 qCWarning(SYCOCA) << "Parse error in " << m_docInfo.path << ", line " << result.errorLine << ", col " << result.errorColumn << ": "
452 << result.errorMessage;
453 file.close();
454 return doc;
455 }
456 file.close();
457
458 tagBaseDir(doc, QStringLiteral("MergeFile"), m_docInfo.baseDir);
459 tagBasePath(doc, QStringLiteral("MergeFile"), m_docInfo.path);
460 tagBaseDir(doc, QStringLiteral("MergeDir"), m_docInfo.baseDir);
461 tagBaseDir(doc, QStringLiteral("DirectoryDir"), m_docInfo.baseDir);
462 tagBaseDir(doc, QStringLiteral("AppDir"), m_docInfo.baseDir);
463 tagBaseDir(doc, QStringLiteral("LegacyDir"), m_docInfo.baseDir);
464
465 return doc;
466}
467
468void VFolderMenu::mergeFile(QDomElement &parent, const QDomNode &mergeHere)
469{
470 // qCDebug(SYCOCA) << m_docInfo.path;
471 QDomDocument doc = loadDoc();
472
473 QDomElement docElem = doc.documentElement();
474 QDomNode n = docElem.firstChild();
475 QDomNode last = mergeHere;
476 while (!n.isNull()) {
477 QDomElement e = n.toElement(); // try to convert the node to an element.
479
480 if (e.isNull()) {
481 // Skip
482 }
483 // The spec says we must ignore any Name nodes
484 else if (e.tagName() != QLatin1String("Name")) {
485 parent.insertAfter(n, last);
486 last = n;
487 }
488
489 docElem.removeChild(n);
490 n = next;
491 }
492}
493
494void VFolderMenu::mergeMenus(QDomElement &docElem, QString &name)
495{
497 QMap<QString, QDomElement> directoryNodes;
498 QMap<QString, QDomElement> appDirNodes;
499 QMap<QString, QDomElement> directoryDirNodes;
500 QMap<QString, QDomElement> legacyDirNodes;
501 QDomElement defaultLayoutNode;
502 QDomElement layoutNode;
503
504 QDomNode n = docElem.firstChild();
505 while (!n.isNull()) {
506 QDomElement e = n.toElement(); // try to convert the node to an element.
507 if (e.isNull()) {
508 // qCDebug(SYCOCA) << "Empty node";
509 } else if (e.tagName() == QLatin1String("DefaultAppDirs")) {
510 // Replace with m_defaultAppDirs
511 replaceNode(docElem, n, m_defaultAppDirs, QStringLiteral("AppDir"));
512 continue;
513 } else if (e.tagName() == QLatin1String("DefaultDirectoryDirs")) {
514 // Replace with m_defaultDirectoryDirs
515 replaceNode(docElem, n, m_defaultDirectoryDirs, QStringLiteral("DirectoryDir"));
516 continue;
517 } else if (e.tagName() == QLatin1String("DefaultMergeDirs")) {
518 // Replace with m_defaultMergeDirs
519 replaceNode(docElem, n, m_defaultMergeDirs, QStringLiteral("MergeDir"));
520 continue;
521 } else if (e.tagName() == QLatin1String("AppDir")) {
522 // Filter out dupes
523 foldNode(docElem, e, appDirNodes);
524 } else if (e.tagName() == QLatin1String("DirectoryDir")) {
525 // Filter out dupes
526 foldNode(docElem, e, directoryDirNodes);
527 } else if (e.tagName() == QLatin1String("LegacyDir")) {
528 // Filter out dupes
529 foldNode(docElem, e, legacyDirNodes);
530 } else if (e.tagName() == QLatin1String("Directory")) {
531 // Filter out dupes
532 foldNode(docElem, e, directoryNodes);
533 } else if (e.tagName() == QLatin1String("Move")) {
534 // Filter out dupes
535 QString orig;
536 QDomNode n2 = e.firstChild();
537 while (!n2.isNull()) {
538 QDomElement e2 = n2.toElement(); // try to convert the node to an element.
539 if (e2.tagName() == QLatin1String("Old")) {
540 orig = e2.text();
541 break;
542 }
543 n2 = n2.nextSibling();
544 }
545 foldNode(docElem, e, appDirNodes, orig);
546 } else if (e.tagName() == QLatin1String("Menu")) {
548 mergeMenus(e, name);
549 QMap<QString, QDomElement>::iterator it = menuNodes.find(name);
550 if (it != menuNodes.end()) {
551 QDomElement docElem2 = *it;
552 QDomNode n2 = docElem2.firstChild();
553 QDomNode first = e.firstChild();
554 while (!n2.isNull()) {
555 QDomElement e2 = n2.toElement(); // try to convert the node to an element.
556 QDomNode n3 = n2.nextSibling();
557 e.insertBefore(n2, first);
558 docElem2.removeChild(n2);
559 n2 = n3;
560 }
561 // We still have duplicated Name entries
562 // but we don't care about that
563
564 docElem.removeChild(docElem2);
565 menuNodes.erase(it);
566 }
567 menuNodes.insert(name, e);
568 } else if (e.tagName() == QLatin1String("MergeFile")) {
569 if ((e.attribute(QStringLiteral("type")) == QLatin1String("parent"))) {
570 // Ignore e.text(), as per the standard. We'll just look up the parent (more global) xml file.
571 pushDocInfoParent(e.attribute(QStringLiteral("__BasePath")), e.attribute(QStringLiteral("__BaseDir")));
572 } else {
573 pushDocInfo(e.text(), e.attribute(QStringLiteral("__BaseDir")));
574 }
575
576 if (!m_docInfo.path.isEmpty()) {
577 mergeFile(docElem, n);
578 }
579 popDocInfo();
580
581 QDomNode last = n;
582 n = n.nextSibling();
583 docElem.removeChild(last); // Remove the MergeFile node
584 continue;
585 } else if (e.tagName() == QLatin1String("MergeDir")) {
586 const QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")), true);
587 Q_ASSERT(dir.endsWith(QLatin1Char('/')));
588
589 const bool relative = QDir::isRelativePath(dir);
590 const QStringList dirs =
592 for (const QString &menuDir : dirs) {
593 registerDirectory(menuDir);
594 }
595
596 QStringList fileList;
597 for (const QString &menuDir : dirs) {
598 const QStringList fileNames = QDir(menuDir).entryList(QStringList() << QStringLiteral("*.menu"));
599 for (const QString &file : fileNames) {
600 const QString fileToAdd = relative ? dir + file : menuDir + file;
601 if (!fileList.contains(fileToAdd)) {
602 fileList.append(fileToAdd);
603 }
604 }
605 }
606
607 for (const QString &file : std::as_const(fileList)) {
608 pushDocInfo(file);
609 mergeFile(docElem, n);
610 popDocInfo();
611 }
612
613 QDomNode last = n;
614 n = n.nextSibling();
615 docElem.removeChild(last); // Remove the MergeDir node
616
617 continue;
618 } else if (e.tagName() == QLatin1String("Name")) {
619 name = e.text();
620 } else if (e.tagName() == QLatin1String("DefaultLayout")) {
621 if (!defaultLayoutNode.isNull()) {
622 docElem.removeChild(defaultLayoutNode);
623 }
624 defaultLayoutNode = e;
625 } else if (e.tagName() == QLatin1String("Layout")) {
626 if (!layoutNode.isNull()) {
627 docElem.removeChild(layoutNode);
628 }
629 layoutNode = e;
630 }
631 n = n.nextSibling();
632 }
633}
634
635static QString makeRelative(const QString &dir)
636{
637 const QString canonical = QDir(dir).canonicalPath();
639 for (const QString &base : list) {
640 if (canonical.startsWith(base)) {
641 return canonical.mid(base.length() + 1);
642 }
643 }
644 return dir;
645}
646
647void VFolderMenu::pushDocInfo(const QString &fileName, const QString &baseDir)
648{
649 m_docInfoStack.push(m_docInfo);
650 if (!baseDir.isEmpty()) {
651 if (!QDir::isRelativePath(baseDir)) {
652 m_docInfo.baseDir = makeRelative(baseDir);
653 } else {
654 m_docInfo.baseDir = baseDir;
655 }
656 }
657
658 QString baseName = fileName;
659 if (!QDir::isRelativePath(baseName)) {
660 registerFile(baseName);
661 } else {
662 baseName = m_docInfo.baseDir + baseName;
663 }
664
665 m_docInfo.path = locateMenuFile(fileName);
666 if (m_docInfo.path.isEmpty()) {
667 m_docInfo.baseDir.clear();
668 m_docInfo.baseName.clear();
669 qCDebug(SYCOCA) << "Menu" << fileName << "not found.";
670 return;
671 }
672 qCDebug(SYCOCA) << "Found menu file" << m_docInfo.path;
673 int i;
674 i = baseName.lastIndexOf(QLatin1Char('/'));
675 if (i > 0) {
676 m_docInfo.baseDir = baseName.left(i + 1);
677 m_docInfo.baseName = baseName.mid(i + 1, baseName.length() - i - 6);
678 } else {
679 m_docInfo.baseDir.clear();
680 m_docInfo.baseName = baseName.left(baseName.length() - 5);
681 }
682}
683
684void VFolderMenu::pushDocInfoParent(const QString &basePath, const QString &baseDir)
685{
686 m_docInfoStack.push(m_docInfo);
687
688 m_docInfo.baseDir = baseDir;
689
690 QString fileName = basePath.mid(basePath.lastIndexOf(QLatin1Char('/')) + 1);
691 m_docInfo.baseName = fileName.left(fileName.length() - 5); // without ".menu"
692 QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName);
693
695
696 // Remove anything "more local" than basePath.
697 while (!result.isEmpty() && (result.at(0) != basePath)) {
698 result.removeFirst();
699 }
700
701 if (result.count() <= 1) {
702 m_docInfo.path.clear(); // No parent found
703 return;
704 }
705 // Now result.at(0) == basePath, take the next one, i.e. the one in the parent dir.
706 m_docInfo.path = result.at(1);
707}
708
709void VFolderMenu::popDocInfo()
710{
711 m_docInfo = m_docInfoStack.pop();
712}
713
714QString VFolderMenu::locateMenuFile(const QString &fileName)
715{
716 if (!QDir::isRelativePath(fileName)) {
717 if (QFile::exists(fileName)) {
718 return fileName;
719 }
720 return QString();
721 }
722
723 QString result;
724
725 QString xdgMenuPrefix = QString::fromLocal8Bit(qgetenv("XDG_MENU_PREFIX"));
726 if (!xdgMenuPrefix.isEmpty()) {
727 QFileInfo fileInfo(fileName);
728
729 QString fileNameOnly = fileInfo.fileName();
730 if (!fileNameOnly.startsWith(xdgMenuPrefix)) {
731 fileNameOnly = xdgMenuPrefix + fileNameOnly;
732 }
733
734 QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileInfo.path() + QLatin1Char('/') + fileNameOnly);
736 }
737
738 if (result.isEmpty()) {
739 QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName);
741 }
742
743 return result;
744}
745
746QString VFolderMenu::locateDirectoryFile(const QString &fileName)
747{
748 if (fileName.isEmpty()) {
749 return QString();
750 }
751
752 if (!QDir::isRelativePath(fileName)) {
753 if (QFile::exists(fileName)) {
754 return fileName;
755 }
756 return QString();
757 }
758
759 // First location in the list wins
760 for (QStringList::ConstIterator it = m_directoryDirs.constBegin(); it != m_directoryDirs.constEnd(); ++it) {
761 QString tmp = (*it) + fileName;
762 if (QFile::exists(tmp)) {
763 return tmp;
764 }
765 }
766
767 return QString();
768}
769
770void VFolderMenu::initDirs()
771{
773 m_defaultDirectoryDirs =
775}
776
777void VFolderMenu::loadMenu(const QString &fileName)
778{
779 m_defaultMergeDirs.clear();
780
781 if (!fileName.endsWith(QLatin1String(".menu"))) {
782 return;
783 }
784
785 pushDocInfo(fileName);
786 m_defaultMergeDirs << QStringLiteral("applications-merged/");
787 m_doc = loadDoc();
788 popDocInfo();
789
790 if (m_doc.isNull()) {
791 if (m_docInfo.path.isEmpty()) {
792 qCritical() << fileName << " not found in " << m_allDirectories;
793 } else {
794 qCWarning(SYCOCA) << "Load error (" << m_docInfo.path << ")";
795 }
796 return;
797 }
798
799 QDomElement e = m_doc.documentElement();
801 mergeMenus(e, name);
802}
803
804void VFolderMenu::processCondition(QDomElement &domElem, QHash<QString, KService::Ptr> &items)
805{
806 if (domElem.tagName() == QLatin1String("And")) {
807 QDomNode n = domElem.firstChild();
808 // Look for the first child element
809 while (!n.isNull()) { // loop in case of comments
810 QDomElement e = n.toElement();
811 n = n.nextSibling();
812 if (!e.isNull()) {
813 processCondition(e, items);
814 break; // we only want the first one
815 }
816 }
817
819 while (!n.isNull()) {
820 QDomElement e = n.toElement();
821 if (e.tagName() == QLatin1String("Not")) {
822 // Special handling for "and not"
823 QDomNode n2 = e.firstChild();
824 while (!n2.isNull()) {
825 QDomElement e2 = n2.toElement();
826 andItems.clear();
827 processCondition(e2, andItems);
828 excludeItems(items, andItems);
829 n2 = n2.nextSibling();
830 }
831 } else {
832 andItems.clear();
833 processCondition(e, andItems);
834 matchItems(items, andItems);
835 }
836 n = n.nextSibling();
837 }
838 } else if (domElem.tagName() == QLatin1String("Or")) {
839 QDomNode n = domElem.firstChild();
840 // Look for the first child element
841 while (!n.isNull()) { // loop in case of comments
842 QDomElement e = n.toElement();
843 n = n.nextSibling();
844 if (!e.isNull()) {
845 processCondition(e, items);
846 break; // we only want the first one
847 }
848 }
849
851 while (!n.isNull()) {
852 QDomElement e = n.toElement();
853 if (!e.isNull()) {
854 orItems.clear();
855 processCondition(e, orItems);
856 includeItems(items, orItems);
857 }
858 n = n.nextSibling();
859 }
860 } else if (domElem.tagName() == QLatin1String("Not")) {
861 FOR_ALL_APPLICATIONS(it)
862 {
863 KService::Ptr s = it.value();
864 items.insert(s->menuId(), s);
865 }
866 FOR_ALL_APPLICATIONS_END
867
869 QDomNode n = domElem.firstChild();
870 while (!n.isNull()) {
871 QDomElement e = n.toElement();
872 if (!e.isNull()) {
873 notItems.clear();
874 processCondition(e, notItems);
875 excludeItems(items, notItems);
876 }
877 n = n.nextSibling();
878 }
879 } else if (domElem.tagName() == QLatin1String("Category")) {
880 FOR_CATEGORY(domElem.text(), it)
881 {
882 KService::Ptr s = *it;
883 items.insert(s->menuId(), s);
884 }
885 FOR_CATEGORY_END
886 } else if (domElem.tagName() == QLatin1String("All")) {
887 FOR_ALL_APPLICATIONS(it)
888 {
889 KService::Ptr s = it.value();
890 items.insert(s->menuId(), s);
891 }
892 FOR_ALL_APPLICATIONS_END
893 } else if (domElem.tagName() == QLatin1String("Filename")) {
894 const QString filename = domElem.text();
895 // qCDebug(SYCOCA) << "Adding file" << filename;
896 KService::Ptr s = findApplication(filename);
897 if (s) {
898 items.insert(filename, s);
899 }
900 }
901}
902
903void VFolderMenu::loadApplications(const QString &dir, const QString &prefix)
904{
905 qCDebug(SYCOCA) << "Looking up applications under" << dir;
906
907 QDirIterator it(dir);
908 while (it.hasNext()) {
909 it.next();
910 const QFileInfo fi = it.fileInfo();
911 const QString fn = fi.fileName();
912 if (fi.isDir() && !fi.isSymLink() && !fi.isBundle()) { // same check as in ksycocautils_p.h
913 if (fn == QLatin1String(".") || fn == QLatin1String("..")) {
914 continue;
915 }
916 loadApplications(fi.filePath(), prefix + fn + QLatin1Char('-'));
917 continue;
918 }
919 if (fi.isFile()) {
920 if (!fn.endsWith(QLatin1String(".desktop"))) {
921 continue;
922 }
923 KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath());
924#ifndef NDEBUG
925 if (fn.contains(QLatin1String("fake"))) {
926 qCDebug(SYCOCA) << "createService" << fi.absoluteFilePath() << "returned" << (service ? "valid service" : "NULL SERVICE");
927 }
928#endif
929 if (service) {
930 addApplication(prefix + fn, service);
931 }
932 }
933 }
934}
935
936void VFolderMenu::processLegacyDir(const QString &dir, const QString &relDir, const QString &prefix)
937{
938 // qCDebug(SYCOCA).nospace() << "processLegacyDir(" << dir << ", " << relDir << ", " << prefix << ")";
939
941 QDirIterator it(dir);
942 while (it.hasNext()) {
943 it.next();
944 const QFileInfo fi = it.fileInfo();
945 const QString fn = fi.fileName();
946 if (fi.isDir()) {
947 if (fn == QLatin1String(".") || fn == QLatin1String("..")) {
948 continue;
949 }
950 SubMenu *parentMenu = m_currentMenu;
951
952 m_currentMenu = new SubMenu;
953 m_currentMenu->name = fn;
954 m_currentMenu->directoryFile = fi.absoluteFilePath() + QLatin1String("/.directory");
955
956 parentMenu->subMenus.append(m_currentMenu);
957
958 processLegacyDir(fi.filePath(), relDir + fn + QLatin1Char('/'), prefix);
959 m_currentMenu = parentMenu;
960 continue;
961 }
962 if (fi.isFile() /*&& !fi.isSymLink() ?? */) {
963 if (!fn.endsWith(QLatin1String(".desktop"))) {
964 continue;
965 }
966 KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath());
967 if (service) {
968 const QString id = prefix + fn;
969
970 // TODO: Add legacy category
971 addApplication(id, service);
972 items.insert(service->menuId(), service);
973
974 if (service->categories().isEmpty()) {
975 m_currentMenu->items.insert(id, service);
976 }
977 }
978 }
979 }
980 markUsedApplications(items);
981}
982
983void VFolderMenu::processMenu(QDomElement &docElem, int pass)
984{
985 SubMenu *parentMenu = m_currentMenu;
986 int oldDirectoryDirsCount = m_directoryDirs.count();
987
989 QString directoryFile;
990 bool onlyUnallocated = false;
991 bool isDeleted = false;
992 QDomElement defaultLayoutNode;
993 QDomElement layoutNode;
994
996 QDomNode n = docElem.firstChild();
997 while (!n.isNull()) {
998 QDomElement e = n.toElement(); // try to convert the node to an element.
999 if (e.tagName() == QLatin1String("Name")) {
1000 name = e.text();
1001 } else if (e.tagName() == QLatin1String("Directory")) {
1002 directoryFile = e.text();
1003 } else if (e.tagName() == QLatin1String("DirectoryDir")) {
1004 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")));
1005
1006 m_directoryDirs.prepend(dir);
1007 } else if (e.tagName() == QLatin1String("OnlyUnallocated")) {
1008 onlyUnallocated = true;
1009 } else if (e.tagName() == QLatin1String("NotOnlyUnallocated")) {
1010 onlyUnallocated = false;
1011 } else if (e.tagName() == QLatin1String("Deleted")) {
1012 isDeleted = true;
1013 } else if (e.tagName() == QLatin1String("NotDeleted")) {
1014 isDeleted = false;
1015 } else if (e.tagName() == QLatin1String("DefaultLayout")) {
1016 defaultLayoutNode = e;
1017 } else if (e.tagName() == QLatin1String("Layout")) {
1018 layoutNode = e;
1019 }
1020 n = n.nextSibling();
1021 }
1022
1023 // Setup current menu entry
1024 if (pass == 0) {
1025 m_currentMenu = nullptr;
1026 // Look up menu
1027 if (parentMenu) {
1028 for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
1029 if (menu->name == name) {
1030 m_currentMenu = menu;
1031 break;
1032 }
1033 }
1034 }
1035
1036 if (!m_currentMenu) { // Not found?
1037 // Create menu
1038 m_currentMenu = new SubMenu;
1039 m_currentMenu->name = name;
1040
1041 if (parentMenu) {
1042 parentMenu->subMenus.append(m_currentMenu);
1043 } else {
1044 m_rootMenu = m_currentMenu;
1045 }
1046 }
1047 if (directoryFile.isEmpty()) {
1048 // qCDebug(SYCOCA) << "Menu" << name << "does not specify a directory file.";
1049 }
1050
1051 // Override previous directoryFile iff available
1052 QString tmp = locateDirectoryFile(directoryFile);
1053 if (!tmp.isEmpty()) {
1054 m_currentMenu->directoryFile = tmp;
1055 }
1056 m_currentMenu->isDeleted = isDeleted;
1057
1058 m_currentMenu->defaultLayoutNode = defaultLayoutNode;
1059 m_currentMenu->layoutNode = layoutNode;
1060 } else {
1061 // Look up menu
1062 if (parentMenu) {
1063 for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
1064 if (menu->name == name) {
1065 m_currentMenu = menu;
1066 break;
1067 }
1068 }
1069 } else {
1070 m_currentMenu = m_rootMenu;
1071 }
1072 }
1073
1074 // Process AppDir and LegacyDir
1075 if (pass == 0) {
1077 QDomNode n = docElem.firstChild();
1078 while (!n.isNull()) {
1079 QDomElement e = n.toElement(); // try to convert the node to an element.
1080 if (e.tagName() == QLatin1String("AppDir")) {
1081 createAppsInfo();
1082 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")));
1083
1084 registerDirectory(dir);
1085
1086 loadApplications(dir, QString());
1087 } else if (e.tagName() == QLatin1String("LegacyDir")) {
1088 createAppsInfo();
1089 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")));
1090
1091 QString prefix = e.attributes().namedItem(QStringLiteral("prefix")).toAttr().value();
1092
1093 SubMenu *oldMenu = m_currentMenu;
1094 m_currentMenu = new SubMenu;
1095
1096 registerDirectory(dir);
1097
1098 processLegacyDir(dir, QString(), prefix);
1099
1100 m_legacyNodes.insert(dir, m_currentMenu);
1101 m_currentMenu = oldMenu;
1102 }
1103 n = n.nextSibling();
1104 }
1105 }
1106
1107 loadAppsInfo(); // Update the scope wrt the list of applications
1108
1109 if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated)) {
1110 n = docElem.firstChild();
1111
1112 while (!n.isNull()) {
1113 QDomElement e = n.toElement(); // try to convert the node to an element.
1114 if (e.tagName() == QLatin1String("Include")) {
1116
1117 QDomNode n2 = e.firstChild();
1118 while (!n2.isNull()) {
1119 QDomElement e2 = n2.toElement();
1120 items.clear();
1121 processCondition(e2, items);
1122 if (m_track) {
1123 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("Before <Include>"));
1124 }
1125 includeItems(m_currentMenu->items, items);
1126 excludeItems(m_currentMenu->excludeItems, items);
1127 markUsedApplications(items);
1128
1129 if (m_track) {
1130 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("After <Include>"));
1131 }
1132
1133 n2 = n2.nextSibling();
1134 }
1135 }
1136
1137 else if (e.tagName() == QLatin1String("Exclude")) {
1139
1140 QDomNode n2 = e.firstChild();
1141 while (!n2.isNull()) {
1142 QDomElement e2 = n2.toElement();
1143 items.clear();
1144 processCondition(e2, items);
1145 if (m_track) {
1146 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("Before <Exclude>"));
1147 }
1148 excludeItems(m_currentMenu->items, items);
1149 includeItems(m_currentMenu->excludeItems, items);
1150 if (m_track) {
1151 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("After <Exclude>"));
1152 }
1153 n2 = n2.nextSibling();
1154 }
1155 }
1156
1157 n = n.nextSibling();
1158 }
1159 }
1160
1161 n = docElem.firstChild();
1162 while (!n.isNull()) {
1163 QDomElement e = n.toElement(); // try to convert the node to an element.
1164 if (e.tagName() == QLatin1String("Menu")) {
1165 processMenu(e, pass);
1166 }
1167 // We insert legacy dir in pass 0, this way the order in the .menu-file determines
1168 // which .directory file gets used, but the menu-entries of legacy-menus will always
1169 // have the lowest priority.
1170 // else if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated))
1171 else if (pass == 0) {
1172 if (e.tagName() == QLatin1String("LegacyDir")) {
1173 // Add legacy nodes to Menu structure
1174 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")));
1175 SubMenu *legacyMenu = m_legacyNodes[dir];
1176 if (legacyMenu) {
1177 mergeMenu(m_currentMenu, legacyMenu);
1178 }
1179 }
1180 }
1181 n = n.nextSibling();
1182 }
1183
1184 if (pass == 2) {
1185 n = docElem.firstChild();
1186 while (!n.isNull()) {
1187 QDomElement e = n.toElement(); // try to convert the node to an element.
1188 if (e.tagName() == QLatin1String("Move")) {
1189 QString orig;
1190 QString dest;
1191 QDomNode n2 = e.firstChild();
1192 while (!n2.isNull()) {
1193 QDomElement e2 = n2.toElement(); // try to convert the node to an element.
1194 if (e2.tagName() == QLatin1String("Old")) {
1195 orig = e2.text();
1196 }
1197 if (e2.tagName() == QLatin1String("New")) {
1198 dest = e2.text();
1199 }
1200 n2 = n2.nextSibling();
1201 }
1202 // qCDebug(SYCOCA) << "Moving" << orig << "to" << dest;
1203 if (!orig.isEmpty() && !dest.isEmpty()) {
1204 SubMenu *menu = takeSubMenu(m_currentMenu, orig);
1205 if (menu) {
1206 insertSubMenu(m_currentMenu, dest, menu, true); // Destination has priority
1207 }
1208 }
1209 }
1210 n = n.nextSibling();
1211 }
1212 }
1213
1214 unloadAppsInfo(); // Update the scope wrt the list of applications
1215
1216 while (m_directoryDirs.count() > oldDirectoryDirsCount) {
1217 m_directoryDirs.pop_front();
1218 }
1219
1220 m_currentMenu = parentMenu;
1221}
1222
1223static QString parseAttribute(const QDomElement &e)
1224{
1225 QString option;
1226
1227 const QString SHOW_EMPTY = QStringLiteral("show_empty");
1228 if (e.hasAttribute(SHOW_EMPTY)) {
1229 QString str = e.attribute(SHOW_EMPTY);
1230 if (str == QLatin1String("true")) {
1231 option = QStringLiteral("ME ");
1232 } else if (str == QLatin1String("false")) {
1233 option = QStringLiteral("NME ");
1234 } else {
1235 // qCDebug(SYCOCA)<<" Error in parsing show_empty attribute :"<<str;
1236 }
1237 }
1238 const QString INLINE = QStringLiteral("inline");
1239 if (e.hasAttribute(INLINE)) {
1240 QString str = e.attribute(INLINE);
1241 if (str == QLatin1String("true")) {
1242 option += QLatin1String("I ");
1243 } else if (str == QLatin1String("false")) {
1244 option += QLatin1String("NI ");
1245 } else {
1246 qCDebug(SYCOCA) << " Error in parsing inline attribute :" << str;
1247 }
1248 }
1249 if (e.hasAttribute(QStringLiteral("inline_limit"))) {
1250 bool ok;
1251 int value = e.attribute(QStringLiteral("inline_limit")).toInt(&ok);
1252 if (ok) {
1253 option += QStringLiteral("IL[%1] ").arg(value);
1254 }
1255 }
1256 if (e.hasAttribute(QStringLiteral("inline_header"))) {
1257 QString str = e.attribute(QStringLiteral("inline_header"));
1258 if (str == QLatin1String("true")) {
1259 option += QLatin1String("IH ");
1260 } else if (str == QLatin1String("false")) {
1261 option += QLatin1String("NIH ");
1262 } else {
1263 qCDebug(SYCOCA) << " Error in parsing of inline_header attribute :" << str;
1264 }
1265 }
1266 if (e.hasAttribute(QStringLiteral("inline_alias")) && e.attribute(QStringLiteral("inline_alias")) == QLatin1String("true")) {
1267 QString str = e.attribute(QStringLiteral("inline_alias"));
1268 if (str == QLatin1String("true")) {
1269 option += QLatin1String("IA");
1270 } else if (str == QLatin1String("false")) {
1271 option += QLatin1String("NIA");
1272 } else {
1273 qCDebug(SYCOCA) << " Error in parsing inline_alias attribute :" << str;
1274 }
1275 }
1276 if (!option.isEmpty()) {
1277 option.prepend(QStringLiteral(":O"));
1278 }
1279 return option;
1280}
1281
1282QStringList VFolderMenu::parseLayoutNode(const QDomElement &docElem) const
1283{
1284 QStringList layout;
1285
1286 QString optionDefaultLayout;
1287 if (docElem.tagName() == QLatin1String("DefaultLayout")) {
1288 optionDefaultLayout = parseAttribute(docElem);
1289 }
1290 if (!optionDefaultLayout.isEmpty()) {
1291 layout.append(optionDefaultLayout);
1292 }
1293
1294 bool mergeTagExists = false;
1295 QDomNode n = docElem.firstChild();
1296 while (!n.isNull()) {
1297 QDomElement e = n.toElement(); // try to convert the node to an element.
1298 if (e.tagName() == QLatin1String("Separator")) {
1299 layout.append(QStringLiteral(":S"));
1300 } else if (e.tagName() == QLatin1String("Filename")) {
1301 layout.append(e.text());
1302 } else if (e.tagName() == QLatin1String("Menuname")) {
1303 layout.append(QLatin1Char('/') + e.text());
1304 QString option = parseAttribute(e);
1305 if (!option.isEmpty()) {
1306 layout.append(option);
1307 }
1308 } else if (e.tagName() == QLatin1String("Merge")) {
1309 QString type = e.attributeNode(QStringLiteral("type")).value();
1310 if (type == QLatin1String("files")) {
1311 layout.append(QStringLiteral(":F"));
1312 } else if (type == QLatin1String("menus")) {
1313 layout.append(QStringLiteral(":M"));
1314 } else if (type == QLatin1String("all")) {
1315 layout.append(QStringLiteral(":A"));
1316 }
1317 mergeTagExists = true;
1318 }
1319
1320 n = n.nextSibling();
1321 }
1322
1323 if (!mergeTagExists) {
1324 layout.append(QStringLiteral(":M"));
1325 layout.append(QStringLiteral(":F"));
1326 qCWarning(SYCOCA) << "The menu spec file (" << m_docInfo.path
1327 << ") contains a Layout or DefaultLayout tag without the mandatory Merge tag inside. Please fix it.";
1328 }
1329 return layout;
1330}
1331
1332void VFolderMenu::layoutMenu(VFolderMenu::SubMenu *menu, QStringList defaultLayout) // krazy:exclude=passbyvalue
1333{
1334 if (!menu->defaultLayoutNode.isNull()) {
1335 defaultLayout = parseLayoutNode(menu->defaultLayoutNode);
1336 }
1337
1338 if (menu->layoutNode.isNull()) {
1339 menu->layoutList = defaultLayout;
1340 } else {
1341 menu->layoutList = parseLayoutNode(menu->layoutNode);
1342 if (menu->layoutList.isEmpty()) {
1343 menu->layoutList = defaultLayout;
1344 }
1345 }
1346
1347 for (VFolderMenu::SubMenu *subMenu : std::as_const(menu->subMenus)) {
1348 layoutMenu(subMenu, defaultLayout);
1349 }
1350}
1351
1352void VFolderMenu::markUsedApplications(const QHash<QString, KService::Ptr> &items)
1353{
1354 for (const KService::Ptr &p : items) {
1355 m_usedAppsDict.insert(p->menuId());
1356 }
1357}
1358
1359VFolderMenu::SubMenu *VFolderMenu::parseMenu(const QString &file)
1360{
1361 m_appsInfo = nullptr;
1362
1364 for (QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) {
1365 registerDirectory(*it);
1366 }
1367
1368 loadMenu(file);
1369
1370 delete m_rootMenu;
1371 m_rootMenu = m_currentMenu = nullptr;
1372
1373 QDomElement docElem = m_doc.documentElement();
1374
1375 for (int pass = 0; pass <= 2; pass++) {
1376 // pass 0: load application desktop files
1377 // pass 1: the normal processing
1378 // pass 2: process <OnlyUnallocated> to put unused files into "Lost & Found".
1379 processMenu(docElem, pass);
1380
1381 switch (pass) {
1382 case 0:
1383 // Fill the dictCategories for each AppsInfo in m_appsInfoList,
1384 // in preparation for processMenu pass 1.
1385 buildApplicationIndex(false);
1386 break;
1387 case 1:
1388 // Fill the dictCategories for each AppsInfo in m_appsInfoList,
1389 // with only the unused apps, in preparation for processMenu pass 2.
1390 buildApplicationIndex(true /* unusedOnly */);
1391 break;
1392 case 2: {
1393 QStringList defaultLayout;
1394 defaultLayout << QStringLiteral(":M"); // Sub-Menus
1395 defaultLayout << QStringLiteral(":F"); // Individual entries
1396 layoutMenu(m_rootMenu, defaultLayout);
1397 break;
1398 }
1399 default:
1400 break;
1401 }
1402 }
1403
1404 return m_rootMenu;
1405}
1406
1407void VFolderMenu::setTrackId(const QString &id)
1408{
1409 m_track = !id.isEmpty();
1410 m_trackId = id;
1411}
1412
1413#include "moc_vfolder_menu_p.cpp"
QExplicitlySharedDataPointer< KService > Ptr
A shared data pointer for KService.
Definition kservice.h:49
Type type(const QSqlDatabase &db)
std::optional< QSqlQuery > query(const QString &queryStatement)
KIOCORE_EXPORT QString dir(const QString &fileClass)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
QString name(StandardAction id)
const QList< QKeySequence > & next()
KEDUVOCDOCUMENT_EXPORT QStringList fileNames(const QString &language=QString())
QString canonicalPath() const const
QString cleanPath(const QString &path)
QStringList entryList(Filters filters, SortFlags sort) const const
bool isRelativePath(const QString &path)
void setValue(const QString &v)
QString value() const const
QDomAttr createAttribute(const QString &name)
QDomElement createElement(const QString &tagName)
QDomText createTextNode(const QString &value)
QDomElement documentElement() const const
QDomNodeList elementsByTagName(const QString &tagname) const const
ParseResult setContent(QAnyStringView text, ParseOptions options)
QString attribute(const QString &name, const QString &defValue) const const
QDomAttr attributeNode(const QString &name)
QDomNamedNodeMap attributes() const const
bool hasAttribute(const QString &name) const const
QDomAttr setAttributeNode(const QDomAttr &newAttr)
QString tagName() const const
QString text() const const
QDomNode namedItem(const QString &name) const const
QDomNode appendChild(const QDomNode &newChild)
QDomNode firstChild() const const
QDomNode insertAfter(const QDomNode &newChild, const QDomNode &refChild)
QDomNode insertBefore(const QDomNode &newChild, const QDomNode &refChild)
bool isNull() const const
QDomNode nextSibling() const const
QDomDocument ownerDocument() const const
QDomNode removeChild(const QDomNode &oldChild)
QDomAttr toAttr() const const
QDomElement toElement() const const
int count() const const
QDomNode item(int index) const const
bool exists() const const
QString absoluteFilePath() const const
QString fileName() const const
QString filePath() const const
bool isBundle() const const
bool isDir() const const
bool isFile() const const
void clear()
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
iterator begin()
qsizetype count() const const
iterator end()
bool isEmpty() const const
void removeFirst()
iterator end()
iterator erase(const_iterator first, const_iterator last)
iterator find(const Key &key)
iterator insert(const Key &key, const T &value)
bool hasNext() const const
QString locate(StandardLocation type, const QString &fileName, LocateOptions options)
QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options)
QStringList standardLocations(StandardLocation type)
QString & append(QChar ch)
QString arg(Args &&... args) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString fromLocal8Bit(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
QString left(qsizetype n) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString & prepend(QChar ch)
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
void sort(Qt::CaseSensitivity cs)
CaseInsensitive
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:00:21 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.