Warning, file /frameworks/kservice/src/sycoca/vfolder_menu.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
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 "kbuildservicefactory_p.h" 0009 #include "kbuildsycocainterface_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 QString errorMsg; 0450 int errorRow; 0451 int errorCol; 0452 if (!doc.setContent(&file, &errorMsg, &errorRow, &errorCol)) { 0453 qCWarning(SYCOCA) << "Parse error in " << m_docInfo.path << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg; 0454 file.close(); 0455 return doc; 0456 } 0457 file.close(); 0458 0459 tagBaseDir(doc, QStringLiteral("MergeFile"), m_docInfo.baseDir); 0460 tagBasePath(doc, QStringLiteral("MergeFile"), m_docInfo.path); 0461 tagBaseDir(doc, QStringLiteral("MergeDir"), m_docInfo.baseDir); 0462 tagBaseDir(doc, QStringLiteral("DirectoryDir"), m_docInfo.baseDir); 0463 tagBaseDir(doc, QStringLiteral("AppDir"), m_docInfo.baseDir); 0464 tagBaseDir(doc, QStringLiteral("LegacyDir"), m_docInfo.baseDir); 0465 0466 return doc; 0467 } 0468 0469 void VFolderMenu::mergeFile(QDomElement &parent, const QDomNode &mergeHere) 0470 { 0471 // qCDebug(SYCOCA) << m_docInfo.path; 0472 QDomDocument doc = loadDoc(); 0473 0474 QDomElement docElem = doc.documentElement(); 0475 QDomNode n = docElem.firstChild(); 0476 QDomNode last = mergeHere; 0477 while (!n.isNull()) { 0478 QDomElement e = n.toElement(); // try to convert the node to an element. 0479 QDomNode next = n.nextSibling(); 0480 0481 if (e.isNull()) { 0482 // Skip 0483 } 0484 // The spec says we must ignore any Name nodes 0485 else if (e.tagName() != QLatin1String("Name")) { 0486 parent.insertAfter(n, last); 0487 last = n; 0488 } 0489 0490 docElem.removeChild(n); 0491 n = next; 0492 } 0493 } 0494 0495 void VFolderMenu::mergeMenus(QDomElement &docElem, QString &name) 0496 { 0497 QMap<QString, QDomElement> menuNodes; 0498 QMap<QString, QDomElement> directoryNodes; 0499 QMap<QString, QDomElement> appDirNodes; 0500 QMap<QString, QDomElement> directoryDirNodes; 0501 QMap<QString, QDomElement> legacyDirNodes; 0502 QDomElement defaultLayoutNode; 0503 QDomElement layoutNode; 0504 0505 QDomNode n = docElem.firstChild(); 0506 while (!n.isNull()) { 0507 QDomElement e = n.toElement(); // try to convert the node to an element. 0508 if (e.isNull()) { 0509 // qCDebug(SYCOCA) << "Empty node"; 0510 } else if (e.tagName() == QLatin1String("DefaultAppDirs")) { 0511 // Replace with m_defaultAppDirs 0512 replaceNode(docElem, n, m_defaultAppDirs, QStringLiteral("AppDir")); 0513 continue; 0514 } else if (e.tagName() == QLatin1String("DefaultDirectoryDirs")) { 0515 // Replace with m_defaultDirectoryDirs 0516 replaceNode(docElem, n, m_defaultDirectoryDirs, QStringLiteral("DirectoryDir")); 0517 continue; 0518 } else if (e.tagName() == QLatin1String("DefaultMergeDirs")) { 0519 // Replace with m_defaultMergeDirs 0520 replaceNode(docElem, n, m_defaultMergeDirs, QStringLiteral("MergeDir")); 0521 continue; 0522 } else if (e.tagName() == QLatin1String("AppDir")) { 0523 // Filter out dupes 0524 foldNode(docElem, e, appDirNodes); 0525 } else if (e.tagName() == QLatin1String("DirectoryDir")) { 0526 // Filter out dupes 0527 foldNode(docElem, e, directoryDirNodes); 0528 } else if (e.tagName() == QLatin1String("LegacyDir")) { 0529 // Filter out dupes 0530 foldNode(docElem, e, legacyDirNodes); 0531 } else if (e.tagName() == QLatin1String("Directory")) { 0532 // Filter out dupes 0533 foldNode(docElem, e, directoryNodes); 0534 } else if (e.tagName() == QLatin1String("Move")) { 0535 // Filter out dupes 0536 QString orig; 0537 QDomNode n2 = e.firstChild(); 0538 while (!n2.isNull()) { 0539 QDomElement e2 = n2.toElement(); // try to convert the node to an element. 0540 if (e2.tagName() == QLatin1String("Old")) { 0541 orig = e2.text(); 0542 break; 0543 } 0544 n2 = n2.nextSibling(); 0545 } 0546 foldNode(docElem, e, appDirNodes, orig); 0547 } else if (e.tagName() == QLatin1String("Menu")) { 0548 QString name; 0549 mergeMenus(e, name); 0550 QMap<QString, QDomElement>::iterator it = menuNodes.find(name); 0551 if (it != menuNodes.end()) { 0552 QDomElement docElem2 = *it; 0553 QDomNode n2 = docElem2.firstChild(); 0554 QDomNode first = e.firstChild(); 0555 while (!n2.isNull()) { 0556 QDomElement e2 = n2.toElement(); // try to convert the node to an element. 0557 QDomNode n3 = n2.nextSibling(); 0558 e.insertBefore(n2, first); 0559 docElem2.removeChild(n2); 0560 n2 = n3; 0561 } 0562 // We still have duplicated Name entries 0563 // but we don't care about that 0564 0565 docElem.removeChild(docElem2); 0566 menuNodes.erase(it); 0567 } 0568 menuNodes.insert(name, e); 0569 } else if (e.tagName() == QLatin1String("MergeFile")) { 0570 if ((e.attribute(QStringLiteral("type")) == QLatin1String("parent"))) { 0571 // Ignore e.text(), as per the standard. We'll just look up the parent (more global) xml file. 0572 pushDocInfoParent(e.attribute(QStringLiteral("__BasePath")), e.attribute(QStringLiteral("__BaseDir"))); 0573 } else { 0574 pushDocInfo(e.text(), e.attribute(QStringLiteral("__BaseDir"))); 0575 } 0576 0577 if (!m_docInfo.path.isEmpty()) { 0578 mergeFile(docElem, n); 0579 } 0580 popDocInfo(); 0581 0582 QDomNode last = n; 0583 n = n.nextSibling(); 0584 docElem.removeChild(last); // Remove the MergeFile node 0585 continue; 0586 } else if (e.tagName() == QLatin1String("MergeDir")) { 0587 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir")), true); 0588 Q_ASSERT(dir.endsWith(QLatin1Char('/'))); 0589 0590 const bool relative = QDir::isRelativePath(dir); 0591 const QStringList dirs = 0592 QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + dir, QStandardPaths::LocateDirectory); 0593 for (const QString &menuDir : dirs) { 0594 registerDirectory(menuDir); 0595 } 0596 0597 QStringList fileList; 0598 for (const QString &menuDir : dirs) { 0599 const QStringList fileNames = QDir(menuDir).entryList(QStringList() << QStringLiteral("*.menu")); 0600 for (const QString &file : fileNames) { 0601 const QString fileToAdd = relative ? dir + file : menuDir + file; 0602 if (!fileList.contains(fileToAdd)) { 0603 fileList.append(fileToAdd); 0604 } 0605 } 0606 } 0607 0608 for (const QString &file : std::as_const(fileList)) { 0609 pushDocInfo(file); 0610 mergeFile(docElem, n); 0611 popDocInfo(); 0612 } 0613 0614 QDomNode last = n; 0615 n = n.nextSibling(); 0616 docElem.removeChild(last); // Remove the MergeDir node 0617 0618 continue; 0619 } else if (e.tagName() == QLatin1String("Name")) { 0620 name = e.text(); 0621 } else if (e.tagName() == QLatin1String("DefaultLayout")) { 0622 if (!defaultLayoutNode.isNull()) { 0623 docElem.removeChild(defaultLayoutNode); 0624 } 0625 defaultLayoutNode = e; 0626 } else if (e.tagName() == QLatin1String("Layout")) { 0627 if (!layoutNode.isNull()) { 0628 docElem.removeChild(layoutNode); 0629 } 0630 layoutNode = e; 0631 } 0632 n = n.nextSibling(); 0633 } 0634 } 0635 0636 static QString makeRelative(const QString &dir) 0637 { 0638 const QString canonical = QDir(dir).canonicalPath(); 0639 const auto list = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus"), QStandardPaths::LocateDirectory); 0640 for (const QString &base : list) { 0641 if (canonical.startsWith(base)) { 0642 return canonical.mid(base.length() + 1); 0643 } 0644 } 0645 return dir; 0646 } 0647 0648 void VFolderMenu::pushDocInfo(const QString &fileName, const QString &baseDir) 0649 { 0650 m_docInfoStack.push(m_docInfo); 0651 if (!baseDir.isEmpty()) { 0652 if (!QDir::isRelativePath(baseDir)) { 0653 m_docInfo.baseDir = makeRelative(baseDir); 0654 } else { 0655 m_docInfo.baseDir = baseDir; 0656 } 0657 } 0658 0659 QString baseName = fileName; 0660 if (!QDir::isRelativePath(baseName)) { 0661 registerFile(baseName); 0662 } else { 0663 baseName = m_docInfo.baseDir + baseName; 0664 } 0665 0666 m_docInfo.path = locateMenuFile(fileName); 0667 if (m_docInfo.path.isEmpty()) { 0668 m_docInfo.baseDir.clear(); 0669 m_docInfo.baseName.clear(); 0670 qCDebug(SYCOCA) << "Menu" << fileName << "not found."; 0671 return; 0672 } 0673 qCDebug(SYCOCA) << "Found menu file" << m_docInfo.path; 0674 int i; 0675 i = baseName.lastIndexOf(QLatin1Char('/')); 0676 if (i > 0) { 0677 m_docInfo.baseDir = baseName.left(i + 1); 0678 m_docInfo.baseName = baseName.mid(i + 1, baseName.length() - i - 6); 0679 } else { 0680 m_docInfo.baseDir.clear(); 0681 m_docInfo.baseName = baseName.left(baseName.length() - 5); 0682 } 0683 } 0684 0685 void VFolderMenu::pushDocInfoParent(const QString &basePath, const QString &baseDir) 0686 { 0687 m_docInfoStack.push(m_docInfo); 0688 0689 m_docInfo.baseDir = baseDir; 0690 0691 QString fileName = basePath.mid(basePath.lastIndexOf(QLatin1Char('/')) + 1); 0692 m_docInfo.baseName = fileName.left(fileName.length() - 5); // without ".menu" 0693 QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName); 0694 0695 QStringList result = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + baseName); 0696 0697 // Remove anything "more local" than basePath. 0698 while (!result.isEmpty() && (result.at(0) != basePath)) { 0699 result.removeFirst(); 0700 } 0701 0702 if (result.count() <= 1) { 0703 m_docInfo.path.clear(); // No parent found 0704 return; 0705 } 0706 // Now result.at(0) == basePath, take the next one, i.e. the one in the parent dir. 0707 m_docInfo.path = result.at(1); 0708 } 0709 0710 void VFolderMenu::popDocInfo() 0711 { 0712 m_docInfo = m_docInfoStack.pop(); 0713 } 0714 0715 QString VFolderMenu::locateMenuFile(const QString &fileName) 0716 { 0717 if (!QDir::isRelativePath(fileName)) { 0718 if (QFile::exists(fileName)) { 0719 return fileName; 0720 } 0721 return QString(); 0722 } 0723 0724 QString result; 0725 0726 QString xdgMenuPrefix = QString::fromLocal8Bit(qgetenv("XDG_MENU_PREFIX")); 0727 if (!xdgMenuPrefix.isEmpty()) { 0728 QFileInfo fileInfo(fileName); 0729 0730 QString fileNameOnly = fileInfo.fileName(); 0731 if (!fileNameOnly.startsWith(xdgMenuPrefix)) { 0732 fileNameOnly = xdgMenuPrefix + fileNameOnly; 0733 } 0734 0735 QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileInfo.path() + QLatin1Char('/') + fileNameOnly); 0736 result = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + baseName); 0737 } 0738 0739 if (result.isEmpty()) { 0740 QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName); 0741 result = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QLatin1String("menus/") + baseName); 0742 } 0743 0744 return result; 0745 } 0746 0747 QString VFolderMenu::locateDirectoryFile(const QString &fileName) 0748 { 0749 if (fileName.isEmpty()) { 0750 return QString(); 0751 } 0752 0753 if (!QDir::isRelativePath(fileName)) { 0754 if (QFile::exists(fileName)) { 0755 return fileName; 0756 } 0757 return QString(); 0758 } 0759 0760 // First location in the list wins 0761 for (QStringList::ConstIterator it = m_directoryDirs.constBegin(); it != m_directoryDirs.constEnd(); ++it) { 0762 QString tmp = (*it) + fileName; 0763 if (QFile::exists(tmp)) { 0764 return tmp; 0765 } 0766 } 0767 0768 return QString(); 0769 } 0770 0771 void VFolderMenu::initDirs() 0772 { 0773 m_defaultAppDirs = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation); 0774 m_defaultDirectoryDirs = 0775 QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("desktop-directories"), QStandardPaths::LocateDirectory); 0776 } 0777 0778 void VFolderMenu::loadMenu(const QString &fileName) 0779 { 0780 m_defaultMergeDirs.clear(); 0781 0782 if (!fileName.endsWith(QLatin1String(".menu"))) { 0783 return; 0784 } 0785 0786 pushDocInfo(fileName); 0787 m_defaultMergeDirs << QStringLiteral("applications-merged/"); 0788 m_doc = loadDoc(); 0789 popDocInfo(); 0790 0791 if (m_doc.isNull()) { 0792 if (m_docInfo.path.isEmpty()) { 0793 qCritical() << fileName << " not found in " << m_allDirectories; 0794 } else { 0795 qCWarning(SYCOCA) << "Load error (" << m_docInfo.path << ")"; 0796 } 0797 return; 0798 } 0799 0800 QDomElement e = m_doc.documentElement(); 0801 QString name; 0802 mergeMenus(e, name); 0803 } 0804 0805 void VFolderMenu::processCondition(QDomElement &domElem, QHash<QString, KService::Ptr> &items) 0806 { 0807 if (domElem.tagName() == QLatin1String("And")) { 0808 QDomNode n = domElem.firstChild(); 0809 // Look for the first child element 0810 while (!n.isNull()) { // loop in case of comments 0811 QDomElement e = n.toElement(); 0812 n = n.nextSibling(); 0813 if (!e.isNull()) { 0814 processCondition(e, items); 0815 break; // we only want the first one 0816 } 0817 } 0818 0819 QHash<QString, KService::Ptr> andItems; 0820 while (!n.isNull()) { 0821 QDomElement e = n.toElement(); 0822 if (e.tagName() == QLatin1String("Not")) { 0823 // Special handling for "and not" 0824 QDomNode n2 = e.firstChild(); 0825 while (!n2.isNull()) { 0826 QDomElement e2 = n2.toElement(); 0827 andItems.clear(); 0828 processCondition(e2, andItems); 0829 excludeItems(items, andItems); 0830 n2 = n2.nextSibling(); 0831 } 0832 } else { 0833 andItems.clear(); 0834 processCondition(e, andItems); 0835 matchItems(items, andItems); 0836 } 0837 n = n.nextSibling(); 0838 } 0839 } else if (domElem.tagName() == QLatin1String("Or")) { 0840 QDomNode n = domElem.firstChild(); 0841 // Look for the first child element 0842 while (!n.isNull()) { // loop in case of comments 0843 QDomElement e = n.toElement(); 0844 n = n.nextSibling(); 0845 if (!e.isNull()) { 0846 processCondition(e, items); 0847 break; // we only want the first one 0848 } 0849 } 0850 0851 QHash<QString, KService::Ptr> orItems; 0852 while (!n.isNull()) { 0853 QDomElement e = n.toElement(); 0854 if (!e.isNull()) { 0855 orItems.clear(); 0856 processCondition(e, orItems); 0857 includeItems(items, orItems); 0858 } 0859 n = n.nextSibling(); 0860 } 0861 } else if (domElem.tagName() == QLatin1String("Not")) { 0862 FOR_ALL_APPLICATIONS(it) 0863 { 0864 KService::Ptr s = it.value(); 0865 items.insert(s->menuId(), s); 0866 } 0867 FOR_ALL_APPLICATIONS_END 0868 0869 QHash<QString, KService::Ptr> notItems; 0870 QDomNode n = domElem.firstChild(); 0871 while (!n.isNull()) { 0872 QDomElement e = n.toElement(); 0873 if (!e.isNull()) { 0874 notItems.clear(); 0875 processCondition(e, notItems); 0876 excludeItems(items, notItems); 0877 } 0878 n = n.nextSibling(); 0879 } 0880 } else if (domElem.tagName() == QLatin1String("Category")) { 0881 FOR_CATEGORY(domElem.text(), it) 0882 { 0883 KService::Ptr s = *it; 0884 items.insert(s->menuId(), s); 0885 } 0886 FOR_CATEGORY_END 0887 } else if (domElem.tagName() == QLatin1String("All")) { 0888 FOR_ALL_APPLICATIONS(it) 0889 { 0890 KService::Ptr s = it.value(); 0891 items.insert(s->menuId(), s); 0892 } 0893 FOR_ALL_APPLICATIONS_END 0894 } else if (domElem.tagName() == QLatin1String("Filename")) { 0895 const QString filename = domElem.text(); 0896 // qCDebug(SYCOCA) << "Adding file" << filename; 0897 KService::Ptr s = findApplication(filename); 0898 if (s) { 0899 items.insert(filename, s); 0900 } 0901 } 0902 } 0903 0904 void VFolderMenu::loadApplications(const QString &dir, const QString &prefix) 0905 { 0906 qCDebug(SYCOCA) << "Looking up applications under" << dir; 0907 0908 QDirIterator it(dir); 0909 while (it.hasNext()) { 0910 it.next(); 0911 const QFileInfo fi = it.fileInfo(); 0912 const QString fn = fi.fileName(); 0913 if (fi.isDir() && !fi.isSymLink() && !fi.isBundle()) { // same check as in ksycocautils_p.h 0914 if (fn == QLatin1String(".") || fn == QLatin1String("..")) { 0915 continue; 0916 } 0917 loadApplications(fi.filePath(), prefix + fn + QLatin1Char('-')); 0918 continue; 0919 } 0920 if (fi.isFile()) { 0921 if (!fn.endsWith(QLatin1String(".desktop"))) { 0922 continue; 0923 } 0924 KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath()); 0925 #ifndef NDEBUG 0926 if (fn.contains(QLatin1String("fake"))) { 0927 qCDebug(SYCOCA) << "createService" << fi.absoluteFilePath() << "returned" << (service ? "valid service" : "NULL SERVICE"); 0928 } 0929 #endif 0930 if (service) { 0931 addApplication(prefix + fn, service); 0932 } 0933 } 0934 } 0935 } 0936 0937 void VFolderMenu::processLegacyDir(const QString &dir, const QString &relDir, const QString &prefix) 0938 { 0939 // qCDebug(SYCOCA).nospace() << "processLegacyDir(" << dir << ", " << relDir << ", " << prefix << ")"; 0940 0941 QHash<QString, KService::Ptr> items; 0942 QDirIterator it(dir); 0943 while (it.hasNext()) { 0944 it.next(); 0945 const QFileInfo fi = it.fileInfo(); 0946 const QString fn = fi.fileName(); 0947 if (fi.isDir()) { 0948 if (fn == QLatin1String(".") || fn == QLatin1String("..")) { 0949 continue; 0950 } 0951 SubMenu *parentMenu = m_currentMenu; 0952 0953 m_currentMenu = new SubMenu; 0954 m_currentMenu->name = fn; 0955 m_currentMenu->directoryFile = fi.absoluteFilePath() + QLatin1String("/.directory"); 0956 0957 parentMenu->subMenus.append(m_currentMenu); 0958 0959 processLegacyDir(fi.filePath(), relDir + fn + QLatin1Char('/'), prefix); 0960 m_currentMenu = parentMenu; 0961 continue; 0962 } 0963 if (fi.isFile() /*&& !fi.isSymLink() ?? */) { 0964 if (!fn.endsWith(QLatin1String(".desktop"))) { 0965 continue; 0966 } 0967 KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath()); 0968 if (service) { 0969 const QString id = prefix + fn; 0970 0971 // TODO: Add legacy category 0972 addApplication(id, service); 0973 items.insert(service->menuId(), service); 0974 0975 if (service->categories().isEmpty()) { 0976 m_currentMenu->items.insert(id, service); 0977 } 0978 } 0979 } 0980 } 0981 markUsedApplications(items); 0982 } 0983 0984 void VFolderMenu::processMenu(QDomElement &docElem, int pass) 0985 { 0986 SubMenu *parentMenu = m_currentMenu; 0987 int oldDirectoryDirsCount = m_directoryDirs.count(); 0988 0989 QString name; 0990 QString directoryFile; 0991 bool onlyUnallocated = false; 0992 bool isDeleted = false; 0993 QDomElement defaultLayoutNode; 0994 QDomElement layoutNode; 0995 0996 QDomElement query; 0997 QDomNode n = docElem.firstChild(); 0998 while (!n.isNull()) { 0999 QDomElement e = n.toElement(); // try to convert the node to an element. 1000 if (e.tagName() == QLatin1String("Name")) { 1001 name = e.text(); 1002 } else if (e.tagName() == QLatin1String("Directory")) { 1003 directoryFile = e.text(); 1004 } else if (e.tagName() == QLatin1String("DirectoryDir")) { 1005 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir"))); 1006 1007 m_directoryDirs.prepend(dir); 1008 } else if (e.tagName() == QLatin1String("OnlyUnallocated")) { 1009 onlyUnallocated = true; 1010 } else if (e.tagName() == QLatin1String("NotOnlyUnallocated")) { 1011 onlyUnallocated = false; 1012 } else if (e.tagName() == QLatin1String("Deleted")) { 1013 isDeleted = true; 1014 } else if (e.tagName() == QLatin1String("NotDeleted")) { 1015 isDeleted = false; 1016 } else if (e.tagName() == QLatin1String("DefaultLayout")) { 1017 defaultLayoutNode = e; 1018 } else if (e.tagName() == QLatin1String("Layout")) { 1019 layoutNode = e; 1020 } 1021 n = n.nextSibling(); 1022 } 1023 1024 // Setup current menu entry 1025 if (pass == 0) { 1026 m_currentMenu = nullptr; 1027 // Look up menu 1028 if (parentMenu) { 1029 for (SubMenu *menu : std::as_const(parentMenu->subMenus)) { 1030 if (menu->name == name) { 1031 m_currentMenu = menu; 1032 break; 1033 } 1034 } 1035 } 1036 1037 if (!m_currentMenu) { // Not found? 1038 // Create menu 1039 m_currentMenu = new SubMenu; 1040 m_currentMenu->name = name; 1041 1042 if (parentMenu) { 1043 parentMenu->subMenus.append(m_currentMenu); 1044 } else { 1045 m_rootMenu = m_currentMenu; 1046 } 1047 } 1048 if (directoryFile.isEmpty()) { 1049 // qCDebug(SYCOCA) << "Menu" << name << "does not specify a directory file."; 1050 } 1051 1052 // Override previous directoryFile iff available 1053 QString tmp = locateDirectoryFile(directoryFile); 1054 if (!tmp.isEmpty()) { 1055 m_currentMenu->directoryFile = tmp; 1056 } 1057 m_currentMenu->isDeleted = isDeleted; 1058 1059 m_currentMenu->defaultLayoutNode = defaultLayoutNode; 1060 m_currentMenu->layoutNode = layoutNode; 1061 } else { 1062 // Look up menu 1063 if (parentMenu) { 1064 for (SubMenu *menu : std::as_const(parentMenu->subMenus)) { 1065 if (menu->name == name) { 1066 m_currentMenu = menu; 1067 break; 1068 } 1069 } 1070 } else { 1071 m_currentMenu = m_rootMenu; 1072 } 1073 } 1074 1075 // Process AppDir and LegacyDir 1076 if (pass == 0) { 1077 QDomElement query; 1078 QDomNode n = docElem.firstChild(); 1079 while (!n.isNull()) { 1080 QDomElement e = n.toElement(); // try to convert the node to an element. 1081 if (e.tagName() == QLatin1String("AppDir")) { 1082 createAppsInfo(); 1083 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir"))); 1084 1085 registerDirectory(dir); 1086 1087 loadApplications(dir, QString()); 1088 } else if (e.tagName() == QLatin1String("LegacyDir")) { 1089 createAppsInfo(); 1090 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir"))); 1091 1092 QString prefix = e.attributes().namedItem(QStringLiteral("prefix")).toAttr().value(); 1093 1094 SubMenu *oldMenu = m_currentMenu; 1095 m_currentMenu = new SubMenu; 1096 1097 registerDirectory(dir); 1098 1099 processLegacyDir(dir, QString(), prefix); 1100 1101 m_legacyNodes.insert(dir, m_currentMenu); 1102 m_currentMenu = oldMenu; 1103 } 1104 n = n.nextSibling(); 1105 } 1106 } 1107 1108 loadAppsInfo(); // Update the scope wrt the list of applications 1109 1110 if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated)) { 1111 n = docElem.firstChild(); 1112 1113 while (!n.isNull()) { 1114 QDomElement e = n.toElement(); // try to convert the node to an element. 1115 if (e.tagName() == QLatin1String("Include")) { 1116 QHash<QString, KService::Ptr> items; 1117 1118 QDomNode n2 = e.firstChild(); 1119 while (!n2.isNull()) { 1120 QDomElement e2 = n2.toElement(); 1121 items.clear(); 1122 processCondition(e2, items); 1123 if (m_track) { 1124 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("Before <Include>")); 1125 } 1126 includeItems(m_currentMenu->items, items); 1127 excludeItems(m_currentMenu->excludeItems, items); 1128 markUsedApplications(items); 1129 1130 if (m_track) { 1131 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("After <Include>")); 1132 } 1133 1134 n2 = n2.nextSibling(); 1135 } 1136 } 1137 1138 else if (e.tagName() == QLatin1String("Exclude")) { 1139 QHash<QString, KService::Ptr> items; 1140 1141 QDomNode n2 = e.firstChild(); 1142 while (!n2.isNull()) { 1143 QDomElement e2 = n2.toElement(); 1144 items.clear(); 1145 processCondition(e2, items); 1146 if (m_track) { 1147 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("Before <Exclude>")); 1148 } 1149 excludeItems(m_currentMenu->items, items); 1150 includeItems(m_currentMenu->excludeItems, items); 1151 if (m_track) { 1152 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, QStringLiteral("After <Exclude>")); 1153 } 1154 n2 = n2.nextSibling(); 1155 } 1156 } 1157 1158 n = n.nextSibling(); 1159 } 1160 } 1161 1162 n = docElem.firstChild(); 1163 while (!n.isNull()) { 1164 QDomElement e = n.toElement(); // try to convert the node to an element. 1165 if (e.tagName() == QLatin1String("Menu")) { 1166 processMenu(e, pass); 1167 } 1168 // We insert legacy dir in pass 0, this way the order in the .menu-file determines 1169 // which .directory file gets used, but the menu-entries of legacy-menus will always 1170 // have the lowest priority. 1171 // else if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated)) 1172 else if (pass == 0) { 1173 if (e.tagName() == QLatin1String("LegacyDir")) { 1174 // Add legacy nodes to Menu structure 1175 QString dir = absoluteDir(e.text(), e.attribute(QStringLiteral("__BaseDir"))); 1176 SubMenu *legacyMenu = m_legacyNodes[dir]; 1177 if (legacyMenu) { 1178 mergeMenu(m_currentMenu, legacyMenu); 1179 } 1180 } 1181 } 1182 n = n.nextSibling(); 1183 } 1184 1185 if (pass == 2) { 1186 n = docElem.firstChild(); 1187 while (!n.isNull()) { 1188 QDomElement e = n.toElement(); // try to convert the node to an element. 1189 if (e.tagName() == QLatin1String("Move")) { 1190 QString orig; 1191 QString dest; 1192 QDomNode n2 = e.firstChild(); 1193 while (!n2.isNull()) { 1194 QDomElement e2 = n2.toElement(); // try to convert the node to an element. 1195 if (e2.tagName() == QLatin1String("Old")) { 1196 orig = e2.text(); 1197 } 1198 if (e2.tagName() == QLatin1String("New")) { 1199 dest = e2.text(); 1200 } 1201 n2 = n2.nextSibling(); 1202 } 1203 // qCDebug(SYCOCA) << "Moving" << orig << "to" << dest; 1204 if (!orig.isEmpty() && !dest.isEmpty()) { 1205 SubMenu *menu = takeSubMenu(m_currentMenu, orig); 1206 if (menu) { 1207 insertSubMenu(m_currentMenu, dest, menu, true); // Destination has priority 1208 } 1209 } 1210 } 1211 n = n.nextSibling(); 1212 } 1213 } 1214 1215 unloadAppsInfo(); // Update the scope wrt the list of applications 1216 1217 while (m_directoryDirs.count() > oldDirectoryDirsCount) { 1218 m_directoryDirs.pop_front(); 1219 } 1220 1221 m_currentMenu = parentMenu; 1222 } 1223 1224 static QString parseAttribute(const QDomElement &e) 1225 { 1226 QString option; 1227 1228 const QString SHOW_EMPTY = QStringLiteral("show_empty"); 1229 if (e.hasAttribute(SHOW_EMPTY)) { 1230 QString str = e.attribute(SHOW_EMPTY); 1231 if (str == QLatin1String("true")) { 1232 option = QStringLiteral("ME "); 1233 } else if (str == QLatin1String("false")) { 1234 option = QStringLiteral("NME "); 1235 } else { 1236 // qCDebug(SYCOCA)<<" Error in parsing show_empty attribute :"<<str; 1237 } 1238 } 1239 const QString INLINE = QStringLiteral("inline"); 1240 if (e.hasAttribute(INLINE)) { 1241 QString str = e.attribute(INLINE); 1242 if (str == QLatin1String("true")) { 1243 option += QLatin1String("I "); 1244 } else if (str == QLatin1String("false")) { 1245 option += QLatin1String("NI "); 1246 } else { 1247 qCDebug(SYCOCA) << " Error in parsing inline attribute :" << str; 1248 } 1249 } 1250 if (e.hasAttribute(QStringLiteral("inline_limit"))) { 1251 bool ok; 1252 int value = e.attribute(QStringLiteral("inline_limit")).toInt(&ok); 1253 if (ok) { 1254 option += QStringLiteral("IL[%1] ").arg(value); 1255 } 1256 } 1257 if (e.hasAttribute(QStringLiteral("inline_header"))) { 1258 QString str = e.attribute(QStringLiteral("inline_header")); 1259 if (str == QLatin1String("true")) { 1260 option += QLatin1String("IH "); 1261 } else if (str == QLatin1String("false")) { 1262 option += QLatin1String("NIH "); 1263 } else { 1264 qCDebug(SYCOCA) << " Error in parsing of inline_header attribute :" << str; 1265 } 1266 } 1267 if (e.hasAttribute(QStringLiteral("inline_alias")) && e.attribute(QStringLiteral("inline_alias")) == QLatin1String("true")) { 1268 QString str = e.attribute(QStringLiteral("inline_alias")); 1269 if (str == QLatin1String("true")) { 1270 option += QLatin1String("IA"); 1271 } else if (str == QLatin1String("false")) { 1272 option += QLatin1String("NIA"); 1273 } else { 1274 qCDebug(SYCOCA) << " Error in parsing inline_alias attribute :" << str; 1275 } 1276 } 1277 if (!option.isEmpty()) { 1278 option.prepend(QStringLiteral(":O")); 1279 } 1280 return option; 1281 } 1282 1283 QStringList VFolderMenu::parseLayoutNode(const QDomElement &docElem) const 1284 { 1285 QStringList layout; 1286 1287 QString optionDefaultLayout; 1288 if (docElem.tagName() == QLatin1String("DefaultLayout")) { 1289 optionDefaultLayout = parseAttribute(docElem); 1290 } 1291 if (!optionDefaultLayout.isEmpty()) { 1292 layout.append(optionDefaultLayout); 1293 } 1294 1295 bool mergeTagExists = false; 1296 QDomNode n = docElem.firstChild(); 1297 while (!n.isNull()) { 1298 QDomElement e = n.toElement(); // try to convert the node to an element. 1299 if (e.tagName() == QLatin1String("Separator")) { 1300 layout.append(QStringLiteral(":S")); 1301 } else if (e.tagName() == QLatin1String("Filename")) { 1302 layout.append(e.text()); 1303 } else if (e.tagName() == QLatin1String("Menuname")) { 1304 layout.append(QLatin1Char('/') + e.text()); 1305 QString option = parseAttribute(e); 1306 if (!option.isEmpty()) { 1307 layout.append(option); 1308 } 1309 } else if (e.tagName() == QLatin1String("Merge")) { 1310 QString type = e.attributeNode(QStringLiteral("type")).value(); 1311 if (type == QLatin1String("files")) { 1312 layout.append(QStringLiteral(":F")); 1313 } else if (type == QLatin1String("menus")) { 1314 layout.append(QStringLiteral(":M")); 1315 } else if (type == QLatin1String("all")) { 1316 layout.append(QStringLiteral(":A")); 1317 } 1318 mergeTagExists = true; 1319 } 1320 1321 n = n.nextSibling(); 1322 } 1323 1324 if (!mergeTagExists) { 1325 layout.append(QStringLiteral(":M")); 1326 layout.append(QStringLiteral(":F")); 1327 qCWarning(SYCOCA) << "The menu spec file (" << m_docInfo.path 1328 << ") contains a Layout or DefaultLayout tag without the mandatory Merge tag inside. Please fix it."; 1329 } 1330 return layout; 1331 } 1332 1333 void VFolderMenu::layoutMenu(VFolderMenu::SubMenu *menu, QStringList defaultLayout) // krazy:exclude=passbyvalue 1334 { 1335 if (!menu->defaultLayoutNode.isNull()) { 1336 defaultLayout = parseLayoutNode(menu->defaultLayoutNode); 1337 } 1338 1339 if (menu->layoutNode.isNull()) { 1340 menu->layoutList = defaultLayout; 1341 } else { 1342 menu->layoutList = parseLayoutNode(menu->layoutNode); 1343 if (menu->layoutList.isEmpty()) { 1344 menu->layoutList = defaultLayout; 1345 } 1346 } 1347 1348 for (VFolderMenu::SubMenu *subMenu : std::as_const(menu->subMenus)) { 1349 layoutMenu(subMenu, defaultLayout); 1350 } 1351 } 1352 1353 void VFolderMenu::markUsedApplications(const QHash<QString, KService::Ptr> &items) 1354 { 1355 for (const KService::Ptr &p : items) { 1356 m_usedAppsDict.insert(p->menuId()); 1357 } 1358 } 1359 1360 VFolderMenu::SubMenu *VFolderMenu::parseMenu(const QString &file) 1361 { 1362 m_appsInfo = nullptr; 1363 1364 const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("menus"), QStandardPaths::LocateDirectory); 1365 for (QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) { 1366 registerDirectory(*it); 1367 } 1368 1369 loadMenu(file); 1370 1371 delete m_rootMenu; 1372 m_rootMenu = m_currentMenu = nullptr; 1373 1374 QDomElement docElem = m_doc.documentElement(); 1375 1376 for (int pass = 0; pass <= 2; pass++) { 1377 // pass 0: load application desktop files 1378 // pass 1: the normal processing 1379 // pass 2: process <OnlyUnallocated> to put unused files into "Lost & Found". 1380 processMenu(docElem, pass); 1381 1382 switch (pass) { 1383 case 0: 1384 // Fill the dictCategories for each AppsInfo in m_appsInfoList, 1385 // in preparation for processMenu pass 1. 1386 buildApplicationIndex(false); 1387 break; 1388 case 1: 1389 // Fill the dictCategories for each AppsInfo in m_appsInfoList, 1390 // with only the unused apps, in preparation for processMenu pass 2. 1391 buildApplicationIndex(true /* unusedOnly */); 1392 break; 1393 case 2: { 1394 QStringList defaultLayout; 1395 defaultLayout << QStringLiteral(":M"); // Sub-Menus 1396 defaultLayout << QStringLiteral(":F"); // Individual entries 1397 layoutMenu(m_rootMenu, defaultLayout); 1398 break; 1399 } 1400 default: 1401 break; 1402 } 1403 } 1404 1405 return m_rootMenu; 1406 } 1407 1408 void VFolderMenu::setTrackId(const QString &id) 1409 { 1410 m_track = !id.isEmpty(); 1411 m_trackId = id; 1412 } 1413 1414 #include "moc_vfolder_menu_p.cpp"