File indexing completed on 2024-04-14 03:54:38

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "kbuildsycocainterface_p.h"
0009 #include "kservicefactory_p.h"
0010 #include "sycocadebug.h"
0011 #include "vfolder_menu_p.h"
0012 
0013 #include <kservice.h>
0014 
0015 #include <QDebug>
0016 #include <QDir>
0017 #include <QDirIterator>
0018 #include <QFile>
0019 #include <QMap>
0020 #include <QStandardPaths>
0021 
0022 static void foldNode(QDomElement &docElem, QDomElement &e, QMap<QString, QDomElement> &dupeList, QString s = QString()) // krazy:exclude=passbyvalue
0023 {
0024     if (s.isEmpty()) {
0025         s = e.text();
0026     }
0027     auto it = dupeList.find(s);
0028     if (it != dupeList.end()) {
0029         // qCDebug(SYCOCA) << e.tagName() << "and" << s << "requires combining!";
0030 
0031         docElem.removeChild(*it);
0032         dupeList.erase(it);
0033     }
0034     dupeList.insert(s, e);
0035 }
0036 
0037 static void replaceNode(QDomElement &docElem, QDomNode &node, const QStringList &list, const QString &tag)
0038 {
0039     for (const QString &str : list) {
0040         QDomElement element = docElem.ownerDocument().createElement(tag);
0041         const QDomText txt = docElem.ownerDocument().createTextNode(str);
0042         element.appendChild(txt);
0043         docElem.insertAfter(element, node);
0044     }
0045 
0046     QDomNode next = node.nextSibling();
0047     docElem.removeChild(node);
0048     node = next;
0049     //   qCDebug(SYCOCA) << "Next tag = " << n.toElement().tagName();
0050 }
0051 
0052 void VFolderMenu::registerFile(const QString &file)
0053 {
0054     int i = file.lastIndexOf(QLatin1Char('/'));
0055     if (i < 0) {
0056         return;
0057     }
0058 
0059     QString dir = file.left(i + 1); // Include trailing '/'
0060     registerDirectory(dir);
0061 }
0062 
0063 void VFolderMenu::registerDirectory(const QString &directory)
0064 {
0065     m_allDirectories.append(directory);
0066 }
0067 
0068 QStringList VFolderMenu::allDirectories()
0069 {
0070     if (m_allDirectories.isEmpty()) {
0071         return m_allDirectories;
0072     }
0073     m_allDirectories.sort();
0074 
0075     QStringList::Iterator it = m_allDirectories.begin();
0076     QString previous = *it++;
0077     for (; it != m_allDirectories.end();) {
0078 #ifndef Q_OS_WIN
0079         if ((*it).startsWith(previous))
0080 #else
0081         if ((*it).startsWith(previous, Qt::CaseInsensitive))
0082 #endif
0083         {
0084             it = m_allDirectories.erase(it);
0085         } else {
0086             previous = *it;
0087             ++it;
0088         }
0089     }
0090     return m_allDirectories;
0091 }
0092 
0093 static void track(const QString &menuId,
0094                   const QString &menuName,
0095                   const QHash<QString, KService::Ptr> &includeList,
0096                   const QHash<QString, KService::Ptr> &excludeList,
0097                   const QHash<QString, KService::Ptr> &itemList,
0098                   const QString &comment)
0099 {
0100     if (itemList.contains(menuId)) {
0101         printf("%s: %s INCL %d EXCL %d\n",
0102                qPrintable(menuName),
0103                qPrintable(comment),
0104                includeList.contains(menuId) ? 1 : 0,
0105                excludeList.contains(menuId) ? 1 : 0);
0106     }
0107 }
0108 
0109 void VFolderMenu::includeItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2)
0110 {
0111     for (const KService::Ptr &p : items2) {
0112         items1.insert(p->menuId(), p);
0113     }
0114 }
0115 
0116 void VFolderMenu::matchItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2)
0117 {
0118     const QHash<QString, KService::Ptr> tmpItems1 = items1;
0119     for (const KService::Ptr &p : tmpItems1) {
0120         QString id = p->menuId();
0121         if (!items2.contains(id)) {
0122             items1.remove(id);
0123         }
0124     }
0125 }
0126 
0127 void VFolderMenu::excludeItems(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2)
0128 {
0129     for (const KService::Ptr &p : items2) {
0130         items1.remove(p->menuId());
0131     }
0132 }
0133 
0134 VFolderMenu::SubMenu *VFolderMenu::takeSubMenu(SubMenu *parentMenu, const QString &menuName)
0135 {
0136     const int i = menuName.indexOf(QLatin1Char('/'));
0137     const QString s1 = i > 0 ? menuName.left(i) : menuName;
0138     const QString s2 = menuName.mid(i + 1);
0139 
0140     // Look up menu
0141     for (QList<SubMenu *>::Iterator it = parentMenu->subMenus.begin(); it != parentMenu->subMenus.end(); ++it) {
0142         SubMenu *menu = *it;
0143         if (menu->name == s1) {
0144             if (i == -1) {
0145                 // Take it out
0146                 parentMenu->subMenus.erase(it);
0147                 return menu;
0148             } else {
0149                 return takeSubMenu(menu, s2);
0150             }
0151         }
0152     }
0153     return nullptr; // Not found
0154 }
0155 
0156 void VFolderMenu::mergeMenu(SubMenu *menu1, SubMenu *menu2, bool reversePriority)
0157 {
0158     if (m_track) {
0159         track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QStringLiteral("Before MenuMerge w. %1 (incl)").arg(menu2->name));
0160         track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QStringLiteral("Before MenuMerge w. %1 (excl)").arg(menu2->name));
0161     }
0162     if (reversePriority) {
0163         // Merge menu1 with menu2, menu1 takes precedent
0164         excludeItems(menu2->items, menu1->excludeItems);
0165         includeItems(menu1->items, menu2->items);
0166         excludeItems(menu2->excludeItems, menu1->items);
0167         includeItems(menu1->excludeItems, menu2->excludeItems);
0168     } else {
0169         // Merge menu1 with menu2, menu2 takes precedent
0170         excludeItems(menu1->items, menu2->excludeItems);
0171         includeItems(menu1->items, menu2->items);
0172         includeItems(menu1->excludeItems, menu2->excludeItems);
0173         menu1->isDeleted = menu2->isDeleted;
0174     }
0175     while (!menu2->subMenus.isEmpty()) {
0176         SubMenu *subMenu = menu2->subMenus.takeFirst();
0177         insertSubMenu(menu1, subMenu->name, subMenu, reversePriority);
0178     }
0179 
0180     if (reversePriority) {
0181         // Merge menu1 with menu2, menu1 takes precedent
0182         if (menu1->directoryFile.isEmpty()) {
0183             menu1->directoryFile = menu2->directoryFile;
0184         }
0185         if (menu1->defaultLayoutNode.isNull()) {
0186             menu1->defaultLayoutNode = menu2->defaultLayoutNode;
0187         }
0188         if (menu1->layoutNode.isNull()) {
0189             menu1->layoutNode = menu2->layoutNode;
0190         }
0191     } else {
0192         // Merge menu1 with menu2, menu2 takes precedent
0193         if (!menu2->directoryFile.isEmpty()) {
0194             menu1->directoryFile = menu2->directoryFile;
0195         }
0196         if (!menu2->defaultLayoutNode.isNull()) {
0197             menu1->defaultLayoutNode = menu2->defaultLayoutNode;
0198         }
0199         if (!menu2->layoutNode.isNull()) {
0200             menu1->layoutNode = menu2->layoutNode;
0201         }
0202     }
0203 
0204     if (m_track) {
0205         track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QStringLiteral("After MenuMerge w. %1 (incl)").arg(menu2->name));
0206         track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QStringLiteral("After MenuMerge w. %1 (excl)").arg(menu2->name));
0207     }
0208 
0209     delete menu2;
0210 }
0211 
0212 void VFolderMenu::insertSubMenu(SubMenu *parentMenu, const QString &menuName, SubMenu *newMenu, bool reversePriority)
0213 {
0214     const int i = menuName.indexOf(QLatin1Char('/'));
0215     const QString s1 = menuName.left(i);
0216     const QString s2 = menuName.mid(i + 1);
0217 
0218     // Look up menu
0219     for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
0220         if (menu->name == s1) {
0221             if (i == -1) {
0222                 mergeMenu(menu, newMenu, reversePriority);
0223                 return;
0224             } else {
0225                 insertSubMenu(menu, s2, newMenu, reversePriority);
0226                 return;
0227             }
0228         }
0229     }
0230     if (i == -1) {
0231         // Add it here
0232         newMenu->name = menuName;
0233         parentMenu->subMenus.append(newMenu);
0234     } else {
0235         SubMenu *menu = new SubMenu;
0236         menu->name = s1;
0237         parentMenu->subMenus.append(menu);
0238         insertSubMenu(menu, s2, newMenu);
0239     }
0240 }
0241 
0242 void VFolderMenu::insertService(SubMenu *parentMenu, const QString &name, KService::Ptr newService)
0243 {
0244     const int i = name.indexOf(QLatin1Char('/'));
0245 
0246     if (i == -1) {
0247         // Add it here
0248         parentMenu->items.insert(newService->menuId(), newService);
0249         return;
0250     }
0251 
0252     QString s1 = name.left(i);
0253     QString s2 = name.mid(i + 1);
0254 
0255     // Look up menu
0256     for (SubMenu *menu : std::as_const(parentMenu->subMenus)) {
0257         if (menu->name == s1) {
0258             insertService(menu, s2, newService);
0259             return;
0260         }
0261     }
0262 
0263     SubMenu *menu = new SubMenu;
0264     menu->name = s1;
0265     parentMenu->subMenus.append(menu);
0266     insertService(menu, s2, newService);
0267 }
0268 
0269 VFolderMenu::VFolderMenu(KServiceFactory *serviceFactory, KBuildSycocaInterface *kbuildsycocaInterface)
0270     : m_appsInfo(nullptr)
0271     , m_rootMenu(nullptr)
0272     , m_currentMenu(nullptr)
0273     , m_track(false)
0274     , m_serviceFactory(serviceFactory)
0275     , m_kbuildsycocaInterface(kbuildsycocaInterface)
0276 {
0277     m_usedAppsDict.reserve(797);
0278     initDirs();
0279 }
0280 
0281 VFolderMenu::~VFolderMenu()
0282 {
0283     qDeleteAll(m_appsInfoList);
0284     delete m_rootMenu;
0285 }
0286 // clang-format off
0287 #define FOR_ALL_APPLICATIONS(it) \
0288     for (AppsInfo *info : std::as_const(m_appsInfoStack)) \
0289     { \
0290         QHashIterator<QString,KService::Ptr> it = info->applications; \
0291         while (it.hasNext()) \
0292         { \
0293             it.next();
0294 #define FOR_ALL_APPLICATIONS_END } }
0295 
0296 #define FOR_CATEGORY(category, it) \
0297     for (AppsInfo *info : std::as_const(m_appsInfoStack)) \
0298     { \
0299         const KService::List list = info->dictCategories.value(category); \
0300         for(KService::List::ConstIterator it = list.constBegin(); \
0301                 it != list.constEnd(); ++it) \
0302         {
0303 #define FOR_CATEGORY_END } }
0304 // clang-format on
0305 
0306 KService::Ptr VFolderMenu::findApplication(const QString &relPath)
0307 {
0308     for (AppsInfo *info : std::as_const(m_appsInfoStack)) {
0309         if (info->applications.contains(relPath)) {
0310             KService::Ptr s = info->applications[relPath];
0311             if (s) {
0312                 return s;
0313             }
0314         }
0315     }
0316     return KService::Ptr();
0317 }
0318 
0319 void VFolderMenu::addApplication(const QString &id, KService::Ptr service)
0320 {
0321     service->setMenuId(id);
0322     m_appsInfo->applications.insert(id, service); // replaces, if already there
0323     m_serviceFactory->addEntry(KSycocaEntry::Ptr(service));
0324 }
0325 
0326 void VFolderMenu::buildApplicationIndex(bool unusedOnly)
0327 {
0328     for (AppsInfo *info : std::as_const(m_appsInfoList)) {
0329         info->dictCategories.clear();
0330         QMutableHashIterator<QString, KService::Ptr> it = info->applications;
0331         while (it.hasNext()) {
0332             KService::Ptr s = it.next().value();
0333             if (unusedOnly && m_usedAppsDict.contains(s->menuId())) {
0334                 // Remove and skip this one
0335                 it.remove();
0336                 continue;
0337             }
0338 
0339             const auto categories = s->categories();
0340             for (const QString &cat : categories) {
0341                 info->dictCategories[cat].append(s); // find or insert entry in hash
0342             }
0343         }
0344     }
0345 }
0346 
0347 void VFolderMenu::createAppsInfo()
0348 {
0349     if (m_appsInfo) {
0350         return;
0351     }
0352 
0353     m_appsInfo = new AppsInfo;
0354     m_appsInfoStack.prepend(m_appsInfo);
0355     m_appsInfoList.append(m_appsInfo);
0356     m_currentMenu->apps_info = m_appsInfo;
0357 }
0358 
0359 void VFolderMenu::loadAppsInfo()
0360 {
0361     m_appsInfo = m_currentMenu->apps_info;
0362     if (!m_appsInfo) {
0363         return; // No appsInfo for this menu
0364     }
0365 
0366     if (!m_appsInfoStack.isEmpty() && m_appsInfoStack.first() == m_appsInfo) {
0367         return; // Already added (By createAppsInfo?)
0368     }
0369 
0370     m_appsInfoStack.prepend(m_appsInfo); // Add
0371 }
0372 
0373 void VFolderMenu::unloadAppsInfo()
0374 {
0375     m_appsInfo = m_currentMenu->apps_info;
0376     if (!m_appsInfo) {
0377         return; // No appsInfo for this menu
0378     }
0379 
0380     if (m_appsInfoStack.first() != m_appsInfo) {
0381         return; // Already removed (huh?)
0382     }
0383 
0384     m_appsInfoStack.removeAll(m_appsInfo); // Remove
0385     m_appsInfo = nullptr;
0386 }
0387 
0388 QString VFolderMenu::absoluteDir(const QString &_dir, const QString &baseDir, bool keepRelativeToCfg)
0389 {
0390     QString dir = _dir;
0391     if (QDir::isRelativePath(dir)) {
0392         dir = baseDir + dir;
0393     }
0394 
0395     bool relative = QDir::isRelativePath(dir);
0396     if (relative && !keepRelativeToCfg) {
0397         relative = false;
0398         dir = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + dir, QStandardPaths::LocateDirectory);
0399     }
0400 
0401     if (!relative) {
0402         QString resolved = QDir(dir).canonicalPath();
0403         if (!resolved.isEmpty()) {
0404             dir = resolved;
0405         }
0406     }
0407 
0408     if (!dir.endsWith(QLatin1Char('/'))) {
0409         dir += QLatin1Char('/');
0410     }
0411 
0412     return dir;
0413 }
0414 
0415 static void tagBaseDir(QDomDocument &doc, const QString &tag, const QString &dir)
0416 {
0417     QDomNodeList mergeFileList = doc.elementsByTagName(tag);
0418     for (int i = 0; i < mergeFileList.count(); i++) {
0419         QDomAttr attr = doc.createAttribute(QStringLiteral("__BaseDir"));
0420         attr.setValue(dir);
0421         mergeFileList.item(i).toElement().setAttributeNode(attr);
0422     }
0423 }
0424 
0425 static void tagBasePath(QDomDocument &doc, const QString &tag, const QString &path)
0426 {
0427     QDomNodeList mergeFileList = doc.elementsByTagName(tag);
0428     for (int i = 0; i < mergeFileList.count(); i++) {
0429         QDomAttr attr = doc.createAttribute(QStringLiteral("__BasePath"));
0430         attr.setValue(path);
0431         mergeFileList.item(i).toElement().setAttributeNode(attr);
0432     }
0433 }
0434 
0435 QDomDocument VFolderMenu::loadDoc()
0436 {
0437     QDomDocument doc;
0438     if (m_docInfo.path.isEmpty()) {
0439         return doc;
0440     }
0441     QFile file(m_docInfo.path);
0442     if (!file.open(QIODevice::ReadOnly)) {
0443         qCWarning(SYCOCA) << "Could not open " << m_docInfo.path;
0444         return doc;
0445     }
0446     if (file.size() == 0) {
0447         return doc;
0448     }
0449     const auto result = doc.setContent(&file);
0450     if (!result) {
0451         qCWarning(SYCOCA) << "Parse error in " << m_docInfo.path << ", line " << result.errorLine << ", col " << result.errorColumn << ": "
0452                           << result.errorMessage;
0453         file.close();
0454         return doc;
0455     }
0456     file.close();
0457 
0458     tagBaseDir(doc, QStringLiteral("MergeFile"), m_docInfo.baseDir);
0459     tagBasePath(doc, QStringLiteral("MergeFile"), m_docInfo.path);
0460     tagBaseDir(doc, QStringLiteral("MergeDir"), m_docInfo.baseDir);
0461     tagBaseDir(doc, QStringLiteral("DirectoryDir"), m_docInfo.baseDir);
0462     tagBaseDir(doc, QStringLiteral("AppDir"), m_docInfo.baseDir);
0463     tagBaseDir(doc, QStringLiteral("LegacyDir"), m_docInfo.baseDir);
0464 
0465     return doc;
0466 }
0467 
0468 void VFolderMenu::mergeFile(QDomElement &parent, const QDomNode &mergeHere)
0469 {
0470     // qCDebug(SYCOCA) << m_docInfo.path;
0471     QDomDocument doc = loadDoc();
0472 
0473     QDomElement docElem = doc.documentElement();
0474     QDomNode n = docElem.firstChild();
0475     QDomNode last = mergeHere;
0476     while (!n.isNull()) {
0477         QDomElement e = n.toElement(); // try to convert the node to an element.
0478         QDomNode next = n.nextSibling();
0479 
0480         if (e.isNull()) {
0481             // Skip
0482         }
0483         // The spec says we must ignore any Name nodes
0484         else if (e.tagName() != QLatin1String("Name")) {
0485             parent.insertAfter(n, last);
0486             last = n;
0487         }
0488 
0489         docElem.removeChild(n);
0490         n = next;
0491     }
0492 }
0493 
0494 void VFolderMenu::mergeMenus(QDomElement &docElem, QString &name)
0495 {
0496     QMap<QString, QDomElement> menuNodes;
0497     QMap<QString, QDomElement> directoryNodes;
0498     QMap<QString, QDomElement> appDirNodes;
0499     QMap<QString, QDomElement> directoryDirNodes;
0500     QMap<QString, QDomElement> legacyDirNodes;
0501     QDomElement defaultLayoutNode;
0502     QDomElement layoutNode;
0503 
0504     QDomNode n = docElem.firstChild();
0505     while (!n.isNull()) {
0506         QDomElement e = n.toElement(); // try to convert the node to an element.
0507         if (e.isNull()) {
0508             // qCDebug(SYCOCA) << "Empty node";
0509         } else if (e.tagName() == QLatin1String("DefaultAppDirs")) {
0510             // Replace with m_defaultAppDirs
0511             replaceNode(docElem, n, m_defaultAppDirs, QStringLiteral("AppDir"));
0512             continue;
0513         } else if (e.tagName() == QLatin1String("DefaultDirectoryDirs")) {
0514             // Replace with m_defaultDirectoryDirs
0515             replaceNode(docElem, n, m_defaultDirectoryDirs, QStringLiteral("DirectoryDir"));
0516             continue;
0517         } else if (e.tagName() == QLatin1String("DefaultMergeDirs")) {
0518             // Replace with m_defaultMergeDirs
0519             replaceNode(docElem, n, m_defaultMergeDirs, QStringLiteral("MergeDir"));
0520             continue;
0521         } else if (e.tagName() == QLatin1String("AppDir")) {
0522             // Filter out dupes
0523             foldNode(docElem, e, appDirNodes);
0524         } else if (e.tagName() == QLatin1String("DirectoryDir")) {
0525             // Filter out dupes
0526             foldNode(docElem, e, directoryDirNodes);
0527         } else if (e.tagName() == QLatin1String("LegacyDir")) {
0528             // Filter out dupes
0529             foldNode(docElem, e, legacyDirNodes);
0530         } else if (e.tagName() == QLatin1String("Directory")) {
0531             // Filter out dupes
0532             foldNode(docElem, e, directoryNodes);
0533         } else if (e.tagName() == QLatin1String("Move")) {
0534             // Filter out dupes
0535             QString orig;
0536             QDomNode n2 = e.firstChild();
0537             while (!n2.isNull()) {
0538                 QDomElement e2 = n2.toElement(); // try to convert the node to an element.
0539                 if (e2.tagName() == QLatin1String("Old")) {
0540                     orig = e2.text();
0541                     break;
0542                 }
0543                 n2 = n2.nextSibling();
0544             }
0545             foldNode(docElem, e, appDirNodes, orig);
0546         } else if (e.tagName() == QLatin1String("Menu")) {
0547             QString name;
0548             mergeMenus(e, name);
0549             QMap<QString, QDomElement>::iterator it = menuNodes.find(name);
0550             if (it != menuNodes.end()) {
0551                 QDomElement docElem2 = *it;
0552                 QDomNode n2 = docElem2.firstChild();
0553                 QDomNode first = e.firstChild();
0554                 while (!n2.isNull()) {
0555                     QDomElement e2 = n2.toElement(); // try to convert the node to an element.
0556                     QDomNode n3 = n2.nextSibling();
0557                     e.insertBefore(n2, first);
0558                     docElem2.removeChild(n2);
0559                     n2 = n3;
0560                 }
0561                 // We still have duplicated Name entries
0562                 // but we don't care about that
0563 
0564                 docElem.removeChild(docElem2);
0565                 menuNodes.erase(it);
0566             }
0567             menuNodes.insert(name, e);
0568         } else if (e.tagName() == QLatin1String("MergeFile")) {
0569             if ((e.attribute(QStringLiteral("type")) == QLatin1String("parent"))) {
0570                 // Ignore e.text(), as per the standard. We'll just look up the parent (more global) xml file.
0571                 pushDocInfoParent(e.attribute(QStringLiteral("__BasePath")), e.attribute(QStringLiteral("__BaseDir")));
0572             } else {
0573                 pushDocInfo(e.text(), e.attribute(QStringLiteral("__BaseDir")));
0574             }
0575 
0576             if (!m_docInfo.path.isEmpty()) {
0577                 mergeFile(docElem, n);
0578             }
0579             popDocInfo();
0580 
0581             QDomNode last = n;
0582             n = n.nextSibling();
0583             docElem.removeChild(last); // Remove the MergeFile node
0584             continue;
0585         } else if (e.tagName() == QLatin1String("MergeDir")) {
0586             const QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")), true);
0587             Q_ASSERT(dir.endsWith(QLatin1Char('/')));
0588 
0589             const bool relative = QDir::isRelativePath(dir);
0590             const QStringList dirs =
0591                 QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + dir, QStandardPaths::LocateDirectory);
0592             for (const QString &menuDir : dirs) {
0593                 registerDirectory(menuDir);
0594             }
0595 
0596             QStringList fileList;
0597             for (const QString &menuDir : dirs) {
0598                 const QStringList fileNames = QDir(menuDir).entryList(QStringList() << QStringLiteral("*.menu"));
0599                 for (const QString &file : fileNames) {
0600                     const QString fileToAdd = relative ? dir + file : menuDir + file;
0601                     if (!fileList.contains(fileToAdd)) {
0602                         fileList.append(fileToAdd);
0603                     }
0604                 }
0605             }
0606 
0607             for (const QString &file : std::as_const(fileList)) {
0608                 pushDocInfo(file);
0609                 mergeFile(docElem, n);
0610                 popDocInfo();
0611             }
0612 
0613             QDomNode last = n;
0614             n = n.nextSibling();
0615             docElem.removeChild(last); // Remove the MergeDir node
0616 
0617             continue;
0618         } else if (e.tagName() == QLatin1String("Name")) {
0619             name = e.text();
0620         } else if (e.tagName() == QLatin1String("DefaultLayout")) {
0621             if (!defaultLayoutNode.isNull()) {
0622                 docElem.removeChild(defaultLayoutNode);
0623             }
0624             defaultLayoutNode = e;
0625         } else if (e.tagName() == QLatin1String("Layout")) {
0626             if (!layoutNode.isNull()) {
0627                 docElem.removeChild(layoutNode);
0628             }
0629             layoutNode = e;
0630         }
0631         n = n.nextSibling();
0632     }
0633 }
0634 
0635 static QString makeRelative(const QString &dir)
0636 {
0637     const QString canonical = QDir(dir).canonicalPath();
0638     const auto list = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus"), QStandardPaths::LocateDirectory);
0639     for (const QString &base : list) {
0640         if (canonical.startsWith(base)) {
0641             return canonical.mid(base.length() + 1);
0642         }
0643     }
0644     return dir;
0645 }
0646 
0647 void VFolderMenu::pushDocInfo(const QString &fileName, const QString &baseDir)
0648 {
0649     m_docInfoStack.push(m_docInfo);
0650     if (!baseDir.isEmpty()) {
0651         if (!QDir::isRelativePath(baseDir)) {
0652             m_docInfo.baseDir = makeRelative(baseDir);
0653         } else {
0654             m_docInfo.baseDir = baseDir;
0655         }
0656     }
0657 
0658     QString baseName = fileName;
0659     if (!QDir::isRelativePath(baseName)) {
0660         registerFile(baseName);
0661     } else {
0662         baseName = m_docInfo.baseDir + baseName;
0663     }
0664 
0665     m_docInfo.path = locateMenuFile(fileName);
0666     if (m_docInfo.path.isEmpty()) {
0667         m_docInfo.baseDir.clear();
0668         m_docInfo.baseName.clear();
0669         qCDebug(SYCOCA) << "Menu" << fileName << "not found.";
0670         return;
0671     }
0672     qCDebug(SYCOCA) << "Found menu file" << m_docInfo.path;
0673     int i;
0674     i = baseName.lastIndexOf(QLatin1Char('/'));
0675     if (i > 0) {
0676         m_docInfo.baseDir = baseName.left(i + 1);
0677         m_docInfo.baseName = baseName.mid(i + 1, baseName.length() - i - 6);
0678     } else {
0679         m_docInfo.baseDir.clear();
0680         m_docInfo.baseName = baseName.left(baseName.length() - 5);
0681     }
0682 }
0683 
0684 void VFolderMenu::pushDocInfoParent(const QString &basePath, const QString &baseDir)
0685 {
0686     m_docInfoStack.push(m_docInfo);
0687 
0688     m_docInfo.baseDir = baseDir;
0689 
0690     QString fileName = basePath.mid(basePath.lastIndexOf(QLatin1Char('/')) + 1);
0691     m_docInfo.baseName = fileName.left(fileName.length() - 5); // without ".menu"
0692     QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName);
0693 
0694     QStringList result = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + baseName);
0695 
0696     // Remove anything "more local" than basePath.
0697     while (!result.isEmpty() && (result.at(0) != basePath)) {
0698         result.removeFirst();
0699     }
0700 
0701     if (result.count() <= 1) {
0702         m_docInfo.path.clear(); // No parent found
0703         return;
0704     }
0705     // Now result.at(0) == basePath, take the next one, i.e. the one in the parent dir.
0706     m_docInfo.path = result.at(1);
0707 }
0708 
0709 void VFolderMenu::popDocInfo()
0710 {
0711     m_docInfo = m_docInfoStack.pop();
0712 }
0713 
0714 QString VFolderMenu::locateMenuFile(const QString &fileName)
0715 {
0716     if (!QDir::isRelativePath(fileName)) {
0717         if (QFile::exists(fileName)) {
0718             return fileName;
0719         }
0720         return QString();
0721     }
0722 
0723     QString result;
0724 
0725     QString xdgMenuPrefix = QString::fromLocal8Bit(qgetenv("XDG_MENU_PREFIX"));
0726     if (!xdgMenuPrefix.isEmpty()) {
0727         QFileInfo fileInfo(fileName);
0728 
0729         QString fileNameOnly = fileInfo.fileName();
0730         if (!fileNameOnly.startsWith(xdgMenuPrefix)) {
0731             fileNameOnly = xdgMenuPrefix + fileNameOnly;
0732         }
0733 
0734         QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileInfo.path() + QLatin1Char('/') + fileNameOnly);
0735         result = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + baseName);
0736     }
0737 
0738     if (result.isEmpty()) {
0739         QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName);
0740         result = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + baseName);
0741     }
0742 
0743     return result;
0744 }
0745 
0746 QString VFolderMenu::locateDirectoryFile(const QString &fileName)
0747 {
0748     if (fileName.isEmpty()) {
0749         return QString();
0750     }
0751 
0752     if (!QDir::isRelativePath(fileName)) {
0753         if (QFile::exists(fileName)) {
0754             return fileName;
0755         }
0756         return QString();
0757     }
0758 
0759     // First location in the list wins
0760     for (QStringList::ConstIterator it = m_directoryDirs.constBegin(); it != m_directoryDirs.constEnd(); ++it) {
0761         QString tmp = (*it) + fileName;
0762         if (QFile::exists(tmp)) {
0763             return tmp;
0764         }
0765     }
0766 
0767     return QString();
0768 }
0769 
0770 void VFolderMenu::initDirs()
0771 {
0772     m_defaultAppDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
0773     m_defaultDirectoryDirs =
0774         QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("desktop-directories"), QStandardPaths::LocateDirectory);
0775 }
0776 
0777 void VFolderMenu::loadMenu(const QString &fileName)
0778 {
0779     m_defaultMergeDirs.clear();
0780 
0781     if (!fileName.endsWith(QLatin1String(".menu"))) {
0782         return;
0783     }
0784 
0785     pushDocInfo(fileName);
0786     m_defaultMergeDirs << QStringLiteral("applications-merged/");
0787     m_doc = loadDoc();
0788     popDocInfo();
0789 
0790     if (m_doc.isNull()) {
0791         if (m_docInfo.path.isEmpty()) {
0792             qCritical() << fileName << " not found in " << m_allDirectories;
0793         } else {
0794             qCWarning(SYCOCA) << "Load error (" << m_docInfo.path << ")";
0795         }
0796         return;
0797     }
0798 
0799     QDomElement e = m_doc.documentElement();
0800     QString name;
0801     mergeMenus(e, name);
0802 }
0803 
0804 void VFolderMenu::processCondition(QDomElement &domElem, QHash<QString, KService::Ptr> &items)
0805 {
0806     if (domElem.tagName() == QLatin1String("And")) {
0807         QDomNode n = domElem.firstChild();
0808         // Look for the first child element
0809         while (!n.isNull()) { // loop in case of comments
0810             QDomElement e = n.toElement();
0811             n = n.nextSibling();
0812             if (!e.isNull()) {
0813                 processCondition(e, items);
0814                 break; // we only want the first one
0815             }
0816         }
0817 
0818         QHash<QString, KService::Ptr> andItems;
0819         while (!n.isNull()) {
0820             QDomElement e = n.toElement();
0821             if (e.tagName() == QLatin1String("Not")) {
0822                 // Special handling for "and not"
0823                 QDomNode n2 = e.firstChild();
0824                 while (!n2.isNull()) {
0825                     QDomElement e2 = n2.toElement();
0826                     andItems.clear();
0827                     processCondition(e2, andItems);
0828                     excludeItems(items, andItems);
0829                     n2 = n2.nextSibling();
0830                 }
0831             } else {
0832                 andItems.clear();
0833                 processCondition(e, andItems);
0834                 matchItems(items, andItems);
0835             }
0836             n = n.nextSibling();
0837         }
0838     } else if (domElem.tagName() == QLatin1String("Or")) {
0839         QDomNode n = domElem.firstChild();
0840         // Look for the first child element
0841         while (!n.isNull()) { // loop in case of comments
0842             QDomElement e = n.toElement();
0843             n = n.nextSibling();
0844             if (!e.isNull()) {
0845                 processCondition(e, items);
0846                 break; // we only want the first one
0847             }
0848         }
0849 
0850         QHash<QString, KService::Ptr> orItems;
0851         while (!n.isNull()) {
0852             QDomElement e = n.toElement();
0853             if (!e.isNull()) {
0854                 orItems.clear();
0855                 processCondition(e, orItems);
0856                 includeItems(items, orItems);
0857             }
0858             n = n.nextSibling();
0859         }
0860     } else if (domElem.tagName() == QLatin1String("Not")) {
0861         FOR_ALL_APPLICATIONS(it)
0862         {
0863             KService::Ptr s = it.value();
0864             items.insert(s->menuId(), s);
0865         }
0866         FOR_ALL_APPLICATIONS_END
0867 
0868         QHash<QString, KService::Ptr> notItems;
0869         QDomNode n = domElem.firstChild();
0870         while (!n.isNull()) {
0871             QDomElement e = n.toElement();
0872             if (!e.isNull()) {
0873                 notItems.clear();
0874                 processCondition(e, notItems);
0875                 excludeItems(items, notItems);
0876             }
0877             n = n.nextSibling();
0878         }
0879     } else if (domElem.tagName() == QLatin1String("Category")) {
0880         FOR_CATEGORY(domElem.text(), it)
0881         {
0882             KService::Ptr s = *it;
0883             items.insert(s->menuId(), s);
0884         }
0885         FOR_CATEGORY_END
0886     } else if (domElem.tagName() == QLatin1String("All")) {
0887         FOR_ALL_APPLICATIONS(it)
0888         {
0889             KService::Ptr s = it.value();
0890             items.insert(s->menuId(), s);
0891         }
0892         FOR_ALL_APPLICATIONS_END
0893     } else if (domElem.tagName() == QLatin1String("Filename")) {
0894         const QString filename = domElem.text();
0895         // qCDebug(SYCOCA) << "Adding file" << filename;
0896         KService::Ptr s = findApplication(filename);
0897         if (s) {
0898             items.insert(filename, s);
0899         }
0900     }
0901 }
0902 
0903 void VFolderMenu::loadApplications(const QString &dir, const QString &prefix)
0904 {
0905     qCDebug(SYCOCA) << "Looking up applications under" << dir;
0906 
0907     QDirIterator it(dir);
0908     while (it.hasNext()) {
0909         it.next();
0910         const QFileInfo fi = it.fileInfo();
0911         const QString fn = fi.fileName();
0912         if (fi.isDir() && !fi.isSymLink() && !fi.isBundle()) { // same check as in ksycocautils_p.h
0913             if (fn == QLatin1String(".") || fn == QLatin1String("..")) {
0914                 continue;
0915             }
0916             loadApplications(fi.filePath(), prefix + fn + QLatin1Char('-'));
0917             continue;
0918         }
0919         if (fi.isFile()) {
0920             if (!fn.endsWith(QLatin1String(".desktop"))) {
0921                 continue;
0922             }
0923             KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath());
0924 #ifndef NDEBUG
0925             if (fn.contains(QLatin1String("fake"))) {
0926                 qCDebug(SYCOCA) << "createService" << fi.absoluteFilePath() << "returned" << (service ? "valid service" : "NULL SERVICE");
0927             }
0928 #endif
0929             if (service) {
0930                 addApplication(prefix + fn, service);
0931             }
0932         }
0933     }
0934 }
0935 
0936 void VFolderMenu::processLegacyDir(const QString &dir, const QString &relDir, const QString &prefix)
0937 {
0938     // qCDebug(SYCOCA).nospace() << "processLegacyDir(" << dir << ", " << relDir << ", " << prefix << ")";
0939 
0940     QHash<QString, KService::Ptr> items;
0941     QDirIterator it(dir);
0942     while (it.hasNext()) {
0943         it.next();
0944         const QFileInfo fi = it.fileInfo();
0945         const QString fn = fi.fileName();
0946         if (fi.isDir()) {
0947             if (fn == QLatin1String(".") || fn == QLatin1String("..")) {
0948                 continue;
0949             }
0950             SubMenu *parentMenu = m_currentMenu;
0951 
0952             m_currentMenu = new SubMenu;
0953             m_currentMenu->name = fn;
0954             m_currentMenu->directoryFile = fi.absoluteFilePath() + QLatin1String("/.directory");
0955 
0956             parentMenu->subMenus.append(m_currentMenu);
0957 
0958             processLegacyDir(fi.filePath(), relDir + fn + QLatin1Char('/'), prefix);
0959             m_currentMenu = parentMenu;
0960             continue;
0961         }
0962         if (fi.isFile() /*&& !fi.isSymLink() ?? */) {
0963             if (!fn.endsWith(QLatin1String(".desktop"))) {
0964                 continue;
0965             }
0966             KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath());
0967             if (service) {
0968                 const QString id = prefix + fn;
0969 
0970                 // TODO: Add legacy category
0971                 addApplication(id, service);
0972                 items.insert(service->menuId(), service);
0973 
0974                 if (service->categories().isEmpty()) {
0975                     m_currentMenu->items.insert(id, service);
0976                 }
0977             }
0978         }
0979     }
0980     markUsedApplications(items);
0981 }
0982 
0983 void VFolderMenu::processMenu(QDomElement &docElem, int pass)
0984 {
0985     SubMenu *parentMenu = m_currentMenu;
0986     int oldDirectoryDirsCount = m_directoryDirs.count();
0987 
0988     QString name;
0989     QString directoryFile;
0990     bool onlyUnallocated = false;
0991     bool isDeleted = false;
0992     QDomElement defaultLayoutNode;
0993     QDomElement layoutNode;
0994 
0995     QDomElement query;
0996     QDomNode n = docElem.firstChild();
0997     while (!n.isNull()) {
0998         QDomElement e = n.toElement(); // try to convert the node to an element.
0999         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) {
1076         QDomElement query;
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")) {
1115                 QHash<QString, KService::Ptr> items;
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")) {
1138                 QHash<QString, KService::Ptr> items;
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 
1223 static 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 
1282 QStringList 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 
1332 void 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 
1352 void 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 
1359 VFolderMenu::SubMenu *VFolderMenu::parseMenu(const QString &file)
1360 {
1361     m_appsInfo = nullptr;
1362 
1363     const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus"), QStandardPaths::LocateDirectory);
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 
1407 void VFolderMenu::setTrackId(const QString &id)
1408 {
1409     m_track = !id.isEmpty();
1410     m_trackId = id;
1411 }
1412 
1413 #include "moc_vfolder_menu_p.cpp"