File indexing completed on 2024-12-08 03:41:43
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"