1 | /* |
2 | This file is part of the KDE libraries |
3 | SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.0-only |
6 | */ |
7 | |
8 | #include "kbuildsycocainterface_p.h" |
9 | #include "kservicefactory_p.h" |
10 | #include "sycocadebug.h" |
11 | #include "vfolder_menu_p.h" |
12 | |
13 | #include <kservice.h> |
14 | |
15 | #include <QDebug> |
16 | #include <QDir> |
17 | #include <QDirIterator> |
18 | #include <QFile> |
19 | #include <QMap> |
20 | #include <QStandardPaths> |
21 | |
22 | static void foldNode(QDomElement &docElem, QDomElement &e, QMap<QString, QDomElement> &dupeList, QString s = QString()) // krazy:exclude=passbyvalue |
23 | { |
24 | if (s.isEmpty()) { |
25 | s = e.text(); |
26 | } |
27 | auto it = dupeList.find(key: s); |
28 | if (it != dupeList.end()) { |
29 | // qCDebug(SYCOCA) << e.tagName() << "and" << s << "requires combining!"; |
30 | |
31 | docElem.removeChild(oldChild: *it); |
32 | dupeList.erase(it); |
33 | } |
34 | dupeList.insert(key: s, value: e); |
35 | } |
36 | |
37 | static void replaceNode(QDomElement &docElem, QDomNode &node, const QStringList &list, const QString &tag) |
38 | { |
39 | for (const QString &str : list) { |
40 | QDomElement element = docElem.ownerDocument().createElement(tagName: tag); |
41 | const QDomText txt = docElem.ownerDocument().createTextNode(data: str); |
42 | element.appendChild(newChild: txt); |
43 | docElem.insertAfter(newChild: element, refChild: node); |
44 | } |
45 | |
46 | QDomNode next = node.nextSibling(); |
47 | docElem.removeChild(oldChild: node); |
48 | node = next; |
49 | // qCDebug(SYCOCA) << "Next tag = " << n.toElement().tagName(); |
50 | } |
51 | |
52 | void VFolderMenu::(const QString &file) |
53 | { |
54 | int i = file.lastIndexOf(c: QLatin1Char('/')); |
55 | if (i < 0) { |
56 | return; |
57 | } |
58 | |
59 | QString dir = file.left(n: i + 1); // Include trailing '/' |
60 | registerDirectory(directory: dir); |
61 | } |
62 | |
63 | void VFolderMenu::(const QString &directory) |
64 | { |
65 | m_allDirectories.append(t: directory); |
66 | } |
67 | |
68 | QStringList VFolderMenu::() |
69 | { |
70 | if (m_allDirectories.isEmpty()) { |
71 | return m_allDirectories; |
72 | } |
73 | m_allDirectories.sort(); |
74 | |
75 | QStringList::Iterator it = m_allDirectories.begin(); |
76 | QString previous = *it++; |
77 | for (; it != m_allDirectories.end();) { |
78 | #ifndef Q_OS_WIN |
79 | if ((*it).startsWith(s: previous)) |
80 | #else |
81 | if ((*it).startsWith(previous, Qt::CaseInsensitive)) |
82 | #endif |
83 | { |
84 | it = m_allDirectories.erase(pos: it); |
85 | } else { |
86 | previous = *it; |
87 | ++it; |
88 | } |
89 | } |
90 | return m_allDirectories; |
91 | } |
92 | |
93 | static void track(const QString &, |
94 | const QString &, |
95 | const QHash<QString, KService::Ptr> &includeList, |
96 | const QHash<QString, KService::Ptr> &excludeList, |
97 | const QHash<QString, KService::Ptr> &itemList, |
98 | const QString &) |
99 | { |
100 | if (itemList.contains(key: menuId)) { |
101 | printf(format: "%s: %s INCL %d EXCL %d\n" , |
102 | qPrintable(menuName), |
103 | qPrintable(comment), |
104 | includeList.contains(key: menuId) ? 1 : 0, |
105 | excludeList.contains(key: menuId) ? 1 : 0); |
106 | } |
107 | } |
108 | |
109 | void VFolderMenu::(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2) |
110 | { |
111 | for (const KService::Ptr &p : items2) { |
112 | items1.insert(key: p->menuId(), value: p); |
113 | } |
114 | } |
115 | |
116 | void VFolderMenu::(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2) |
117 | { |
118 | const QHash<QString, KService::Ptr> tmpItems1 = items1; |
119 | for (const KService::Ptr &p : tmpItems1) { |
120 | QString id = p->menuId(); |
121 | if (!items2.contains(key: id)) { |
122 | items1.remove(key: id); |
123 | } |
124 | } |
125 | } |
126 | |
127 | void VFolderMenu::(QHash<QString, KService::Ptr> &items1, const QHash<QString, KService::Ptr> &items2) |
128 | { |
129 | for (const KService::Ptr &p : items2) { |
130 | items1.remove(key: p->menuId()); |
131 | } |
132 | } |
133 | |
134 | VFolderMenu::SubMenu *VFolderMenu::(SubMenu *, const QString &) |
135 | { |
136 | const int i = menuName.indexOf(c: QLatin1Char('/')); |
137 | const QString s1 = i > 0 ? menuName.left(n: i) : menuName; |
138 | const QString s2 = menuName.mid(position: i + 1); |
139 | |
140 | // Look up menu |
141 | for (QList<SubMenu *>::Iterator it = parentMenu->subMenus.begin(); it != parentMenu->subMenus.end(); ++it) { |
142 | SubMenu * = *it; |
143 | if (menu->name == s1) { |
144 | if (i == -1) { |
145 | // Take it out |
146 | parentMenu->subMenus.erase(pos: it); |
147 | return menu; |
148 | } else { |
149 | return takeSubMenu(parentMenu: menu, menuName: s2); |
150 | } |
151 | } |
152 | } |
153 | return nullptr; // Not found |
154 | } |
155 | |
156 | void VFolderMenu::(SubMenu *, SubMenu *, bool reversePriority) |
157 | { |
158 | if (m_track) { |
159 | track(menuId: m_trackId, menuName: menu1->name, includeList: menu1->items, excludeList: menu1->excludeItems, itemList: menu2->items, QStringLiteral("Before MenuMerge w. %1 (incl)" ).arg(a: menu2->name)); |
160 | track(menuId: m_trackId, menuName: menu1->name, includeList: menu1->items, excludeList: menu1->excludeItems, itemList: menu2->excludeItems, QStringLiteral("Before MenuMerge w. %1 (excl)" ).arg(a: menu2->name)); |
161 | } |
162 | if (reversePriority) { |
163 | // Merge menu1 with menu2, menu1 takes precedent |
164 | excludeItems(items1&: menu2->items, items2: menu1->excludeItems); |
165 | includeItems(items1&: menu1->items, items2: menu2->items); |
166 | excludeItems(items1&: menu2->excludeItems, items2: menu1->items); |
167 | includeItems(items1&: menu1->excludeItems, items2: menu2->excludeItems); |
168 | } else { |
169 | // Merge menu1 with menu2, menu2 takes precedent |
170 | excludeItems(items1&: menu1->items, items2: menu2->excludeItems); |
171 | includeItems(items1&: menu1->items, items2: menu2->items); |
172 | includeItems(items1&: menu1->excludeItems, items2: menu2->excludeItems); |
173 | menu1->isDeleted = menu2->isDeleted; |
174 | } |
175 | while (!menu2->subMenus.isEmpty()) { |
176 | SubMenu * = menu2->subMenus.takeFirst(); |
177 | insertSubMenu(parentMenu: menu1, menuName: subMenu->name, newMenu: subMenu, reversePriority); |
178 | } |
179 | |
180 | if (reversePriority) { |
181 | // Merge menu1 with menu2, menu1 takes precedent |
182 | if (menu1->directoryFile.isEmpty()) { |
183 | menu1->directoryFile = menu2->directoryFile; |
184 | } |
185 | if (menu1->defaultLayoutNode.isNull()) { |
186 | menu1->defaultLayoutNode = menu2->defaultLayoutNode; |
187 | } |
188 | if (menu1->layoutNode.isNull()) { |
189 | menu1->layoutNode = menu2->layoutNode; |
190 | } |
191 | } else { |
192 | // Merge menu1 with menu2, menu2 takes precedent |
193 | if (!menu2->directoryFile.isEmpty()) { |
194 | menu1->directoryFile = menu2->directoryFile; |
195 | } |
196 | if (!menu2->defaultLayoutNode.isNull()) { |
197 | menu1->defaultLayoutNode = menu2->defaultLayoutNode; |
198 | } |
199 | if (!menu2->layoutNode.isNull()) { |
200 | menu1->layoutNode = menu2->layoutNode; |
201 | } |
202 | } |
203 | |
204 | if (m_track) { |
205 | track(menuId: m_trackId, menuName: menu1->name, includeList: menu1->items, excludeList: menu1->excludeItems, itemList: menu2->items, QStringLiteral("After MenuMerge w. %1 (incl)" ).arg(a: menu2->name)); |
206 | track(menuId: m_trackId, menuName: menu1->name, includeList: menu1->items, excludeList: menu1->excludeItems, itemList: menu2->excludeItems, QStringLiteral("After MenuMerge w. %1 (excl)" ).arg(a: menu2->name)); |
207 | } |
208 | |
209 | delete menu2; |
210 | } |
211 | |
212 | void VFolderMenu::(SubMenu *, const QString &, SubMenu *, bool reversePriority) |
213 | { |
214 | const int i = menuName.indexOf(c: QLatin1Char('/')); |
215 | const QString s1 = menuName.left(n: i); |
216 | const QString s2 = menuName.mid(position: i + 1); |
217 | |
218 | // Look up menu |
219 | for (SubMenu * : std::as_const(t&: parentMenu->subMenus)) { |
220 | if (menu->name == s1) { |
221 | if (i == -1) { |
222 | mergeMenu(menu1: menu, menu2: newMenu, reversePriority); |
223 | return; |
224 | } else { |
225 | insertSubMenu(parentMenu: menu, menuName: s2, newMenu, reversePriority); |
226 | return; |
227 | } |
228 | } |
229 | } |
230 | if (i == -1) { |
231 | // Add it here |
232 | newMenu->name = menuName; |
233 | parentMenu->subMenus.append(t: newMenu); |
234 | } else { |
235 | SubMenu * = new SubMenu; |
236 | menu->name = s1; |
237 | parentMenu->subMenus.append(t: menu); |
238 | insertSubMenu(parentMenu: menu, menuName: s2, newMenu); |
239 | } |
240 | } |
241 | |
242 | void VFolderMenu::(SubMenu *, const QString &name, KService::Ptr newService) |
243 | { |
244 | const int i = name.indexOf(c: QLatin1Char('/')); |
245 | |
246 | if (i == -1) { |
247 | // Add it here |
248 | parentMenu->items.insert(key: newService->menuId(), value: newService); |
249 | return; |
250 | } |
251 | |
252 | QString s1 = name.left(n: i); |
253 | QString s2 = name.mid(position: i + 1); |
254 | |
255 | // Look up menu |
256 | for (SubMenu * : std::as_const(t&: parentMenu->subMenus)) { |
257 | if (menu->name == s1) { |
258 | insertService(parentMenu: menu, name: s2, newService); |
259 | return; |
260 | } |
261 | } |
262 | |
263 | SubMenu * = new SubMenu; |
264 | menu->name = s1; |
265 | parentMenu->subMenus.append(t: menu); |
266 | insertService(parentMenu: menu, name: s2, newService); |
267 | } |
268 | |
269 | VFolderMenu::(KServiceFactory *serviceFactory, KBuildSycocaInterface *kbuildsycocaInterface) |
270 | : m_appsInfo(nullptr) |
271 | , m_rootMenu(nullptr) |
272 | , m_currentMenu(nullptr) |
273 | , m_track(false) |
274 | , m_serviceFactory(serviceFactory) |
275 | , m_kbuildsycocaInterface(kbuildsycocaInterface) |
276 | { |
277 | m_usedAppsDict.reserve(asize: 797); |
278 | initDirs(); |
279 | } |
280 | |
281 | VFolderMenu::() |
282 | { |
283 | qDeleteAll(c: m_appsInfoList); |
284 | delete m_rootMenu; |
285 | } |
286 | // clang-format off |
287 | #define FOR_ALL_APPLICATIONS(it) \ |
288 | for (AppsInfo *info : std::as_const(m_appsInfoStack)) \ |
289 | { \ |
290 | QHashIterator<QString,KService::Ptr> it = info->applications; \ |
291 | while (it.hasNext()) \ |
292 | { \ |
293 | it.next(); |
294 | #define FOR_ALL_APPLICATIONS_END } } |
295 | |
296 | #define FOR_CATEGORY(category, it) \ |
297 | for (AppsInfo *info : std::as_const(m_appsInfoStack)) \ |
298 | { \ |
299 | const KService::List list = info->dictCategories.value(category); \ |
300 | for(KService::List::ConstIterator it = list.constBegin(); \ |
301 | it != list.constEnd(); ++it) \ |
302 | { |
303 | #define FOR_CATEGORY_END } } |
304 | // clang-format on |
305 | |
306 | KService::Ptr VFolderMenu::(const QString &relPath) |
307 | { |
308 | for (AppsInfo *info : std::as_const(t&: m_appsInfoStack)) { |
309 | if (info->applications.contains(key: relPath)) { |
310 | KService::Ptr s = info->applications[relPath]; |
311 | if (s) { |
312 | return s; |
313 | } |
314 | } |
315 | } |
316 | return KService::Ptr(); |
317 | } |
318 | |
319 | void VFolderMenu::(const QString &id, KService::Ptr service) |
320 | { |
321 | service->setMenuId(id); |
322 | m_appsInfo->applications.insert(key: id, value: service); // replaces, if already there |
323 | m_serviceFactory->addEntry(newEntry: KSycocaEntry::Ptr(service)); |
324 | } |
325 | |
326 | void VFolderMenu::(bool unusedOnly) |
327 | { |
328 | for (AppsInfo *info : std::as_const(t&: m_appsInfoList)) { |
329 | info->dictCategories.clear(); |
330 | QMutableHashIterator<QString, KService::Ptr> it = info->applications; |
331 | while (it.hasNext()) { |
332 | KService::Ptr s = it.next().value(); |
333 | if (unusedOnly && m_usedAppsDict.contains(value: s->menuId())) { |
334 | // Remove and skip this one |
335 | it.remove(); |
336 | continue; |
337 | } |
338 | |
339 | const auto categories = s->categories(); |
340 | for (const QString &cat : categories) { |
341 | info->dictCategories[cat].append(t: s); // find or insert entry in hash |
342 | } |
343 | } |
344 | } |
345 | } |
346 | |
347 | void VFolderMenu::() |
348 | { |
349 | if (m_appsInfo) { |
350 | return; |
351 | } |
352 | |
353 | m_appsInfo = new AppsInfo; |
354 | m_appsInfoStack.prepend(t: m_appsInfo); |
355 | m_appsInfoList.append(t: m_appsInfo); |
356 | m_currentMenu->apps_info = m_appsInfo; |
357 | } |
358 | |
359 | void VFolderMenu::() |
360 | { |
361 | m_appsInfo = m_currentMenu->apps_info; |
362 | if (!m_appsInfo) { |
363 | return; // No appsInfo for this menu |
364 | } |
365 | |
366 | if (!m_appsInfoStack.isEmpty() && m_appsInfoStack.first() == m_appsInfo) { |
367 | return; // Already added (By createAppsInfo?) |
368 | } |
369 | |
370 | m_appsInfoStack.prepend(t: m_appsInfo); // Add |
371 | } |
372 | |
373 | void VFolderMenu::() |
374 | { |
375 | m_appsInfo = m_currentMenu->apps_info; |
376 | if (!m_appsInfo) { |
377 | return; // No appsInfo for this menu |
378 | } |
379 | |
380 | if (m_appsInfoStack.first() != m_appsInfo) { |
381 | return; // Already removed (huh?) |
382 | } |
383 | |
384 | m_appsInfoStack.removeAll(t: m_appsInfo); // Remove |
385 | m_appsInfo = nullptr; |
386 | } |
387 | |
388 | QString VFolderMenu::(const QString &_dir, const QString &baseDir, bool keepRelativeToCfg) |
389 | { |
390 | QString dir = _dir; |
391 | if (QDir::isRelativePath(path: dir)) { |
392 | dir = baseDir + dir; |
393 | } |
394 | |
395 | bool relative = QDir::isRelativePath(path: dir); |
396 | if (relative && !keepRelativeToCfg) { |
397 | relative = false; |
398 | dir = QStandardPaths::locate(type: QStandardPaths::GenericConfigLocation, fileName: QLatin1String("menus/" ) + dir, options: QStandardPaths::LocateDirectory); |
399 | } |
400 | |
401 | if (!relative) { |
402 | QString resolved = QDir(dir).canonicalPath(); |
403 | if (!resolved.isEmpty()) { |
404 | dir = resolved; |
405 | } |
406 | } |
407 | |
408 | if (!dir.endsWith(c: QLatin1Char('/'))) { |
409 | dir += QLatin1Char('/'); |
410 | } |
411 | |
412 | return dir; |
413 | } |
414 | |
415 | static void tagBaseDir(QDomDocument &doc, const QString &tag, const QString &dir) |
416 | { |
417 | QDomNodeList mergeFileList = doc.elementsByTagName(tagname: tag); |
418 | for (int i = 0; i < mergeFileList.count(); i++) { |
419 | QDomAttr attr = doc.createAttribute(QStringLiteral("__BaseDir" )); |
420 | attr.setValue(dir); |
421 | mergeFileList.item(index: i).toElement().setAttributeNode(attr); |
422 | } |
423 | } |
424 | |
425 | static void tagBasePath(QDomDocument &doc, const QString &tag, const QString &path) |
426 | { |
427 | QDomNodeList mergeFileList = doc.elementsByTagName(tagname: tag); |
428 | for (int i = 0; i < mergeFileList.count(); i++) { |
429 | QDomAttr attr = doc.createAttribute(QStringLiteral("__BasePath" )); |
430 | attr.setValue(path); |
431 | mergeFileList.item(index: i).toElement().setAttributeNode(attr); |
432 | } |
433 | } |
434 | |
435 | QDomDocument VFolderMenu::() |
436 | { |
437 | QDomDocument doc; |
438 | if (m_docInfo.path.isEmpty()) { |
439 | return doc; |
440 | } |
441 | QFile file(m_docInfo.path); |
442 | if (!file.open(flags: QIODevice::ReadOnly)) { |
443 | qCWarning(SYCOCA) << "Could not open " << m_docInfo.path; |
444 | return doc; |
445 | } |
446 | if (file.size() == 0) { |
447 | return doc; |
448 | } |
449 | const auto result = doc.setContent(device: &file); |
450 | if (!result) { |
451 | qCWarning(SYCOCA) << "Parse error in " << m_docInfo.path << ", line " << result.errorLine << ", col " << result.errorColumn << ": " |
452 | << result.errorMessage; |
453 | file.close(); |
454 | return doc; |
455 | } |
456 | file.close(); |
457 | |
458 | tagBaseDir(doc, QStringLiteral("MergeFile" ), dir: m_docInfo.baseDir); |
459 | tagBasePath(doc, QStringLiteral("MergeFile" ), path: m_docInfo.path); |
460 | tagBaseDir(doc, QStringLiteral("MergeDir" ), dir: m_docInfo.baseDir); |
461 | tagBaseDir(doc, QStringLiteral("DirectoryDir" ), dir: m_docInfo.baseDir); |
462 | tagBaseDir(doc, QStringLiteral("AppDir" ), dir: m_docInfo.baseDir); |
463 | tagBaseDir(doc, QStringLiteral("LegacyDir" ), dir: m_docInfo.baseDir); |
464 | |
465 | return doc; |
466 | } |
467 | |
468 | void VFolderMenu::(QDomElement &parent, const QDomNode &mergeHere) |
469 | { |
470 | // qCDebug(SYCOCA) << m_docInfo.path; |
471 | QDomDocument doc = loadDoc(); |
472 | |
473 | QDomElement docElem = doc.documentElement(); |
474 | QDomNode n = docElem.firstChild(); |
475 | QDomNode last = mergeHere; |
476 | while (!n.isNull()) { |
477 | QDomElement e = n.toElement(); // try to convert the node to an element. |
478 | QDomNode next = n.nextSibling(); |
479 | |
480 | if (e.isNull()) { |
481 | // Skip |
482 | } |
483 | // The spec says we must ignore any Name nodes |
484 | else if (e.tagName() != QLatin1String("Name" )) { |
485 | parent.insertAfter(newChild: n, refChild: last); |
486 | last = n; |
487 | } |
488 | |
489 | docElem.removeChild(oldChild: n); |
490 | n = next; |
491 | } |
492 | } |
493 | |
494 | void VFolderMenu::(QDomElement &docElem, QString &name) |
495 | { |
496 | QMap<QString, QDomElement> ; |
497 | QMap<QString, QDomElement> directoryNodes; |
498 | QMap<QString, QDomElement> appDirNodes; |
499 | QMap<QString, QDomElement> directoryDirNodes; |
500 | QMap<QString, QDomElement> legacyDirNodes; |
501 | QDomElement defaultLayoutNode; |
502 | QDomElement layoutNode; |
503 | |
504 | QDomNode n = docElem.firstChild(); |
505 | while (!n.isNull()) { |
506 | QDomElement e = n.toElement(); // try to convert the node to an element. |
507 | if (e.isNull()) { |
508 | // qCDebug(SYCOCA) << "Empty node"; |
509 | } else if (e.tagName() == QLatin1String("DefaultAppDirs" )) { |
510 | // Replace with m_defaultAppDirs |
511 | replaceNode(docElem, node&: n, list: m_defaultAppDirs, QStringLiteral("AppDir" )); |
512 | continue; |
513 | } else if (e.tagName() == QLatin1String("DefaultDirectoryDirs" )) { |
514 | // Replace with m_defaultDirectoryDirs |
515 | replaceNode(docElem, node&: n, list: m_defaultDirectoryDirs, QStringLiteral("DirectoryDir" )); |
516 | continue; |
517 | } else if (e.tagName() == QLatin1String("DefaultMergeDirs" )) { |
518 | // Replace with m_defaultMergeDirs |
519 | replaceNode(docElem, node&: n, list: m_defaultMergeDirs, QStringLiteral("MergeDir" )); |
520 | continue; |
521 | } else if (e.tagName() == QLatin1String("AppDir" )) { |
522 | // Filter out dupes |
523 | foldNode(docElem, e, dupeList&: appDirNodes); |
524 | } else if (e.tagName() == QLatin1String("DirectoryDir" )) { |
525 | // Filter out dupes |
526 | foldNode(docElem, e, dupeList&: directoryDirNodes); |
527 | } else if (e.tagName() == QLatin1String("LegacyDir" )) { |
528 | // Filter out dupes |
529 | foldNode(docElem, e, dupeList&: legacyDirNodes); |
530 | } else if (e.tagName() == QLatin1String("Directory" )) { |
531 | // Filter out dupes |
532 | foldNode(docElem, e, dupeList&: directoryNodes); |
533 | } else if (e.tagName() == QLatin1String("Move" )) { |
534 | // Filter out dupes |
535 | QString orig; |
536 | QDomNode n2 = e.firstChild(); |
537 | while (!n2.isNull()) { |
538 | QDomElement e2 = n2.toElement(); // try to convert the node to an element. |
539 | if (e2.tagName() == QLatin1String("Old" )) { |
540 | orig = e2.text(); |
541 | break; |
542 | } |
543 | n2 = n2.nextSibling(); |
544 | } |
545 | foldNode(docElem, e, dupeList&: appDirNodes, s: orig); |
546 | } else if (e.tagName() == QLatin1String("Menu" )) { |
547 | QString name; |
548 | mergeMenus(docElem&: e, name); |
549 | QMap<QString, QDomElement>::iterator it = menuNodes.find(key: name); |
550 | if (it != menuNodes.end()) { |
551 | QDomElement docElem2 = *it; |
552 | QDomNode n2 = docElem2.firstChild(); |
553 | QDomNode first = e.firstChild(); |
554 | while (!n2.isNull()) { |
555 | QDomElement e2 = n2.toElement(); // try to convert the node to an element. |
556 | QDomNode n3 = n2.nextSibling(); |
557 | e.insertBefore(newChild: n2, refChild: first); |
558 | docElem2.removeChild(oldChild: n2); |
559 | n2 = n3; |
560 | } |
561 | // We still have duplicated Name entries |
562 | // but we don't care about that |
563 | |
564 | docElem.removeChild(oldChild: docElem2); |
565 | menuNodes.erase(it); |
566 | } |
567 | menuNodes.insert(key: name, value: e); |
568 | } else if (e.tagName() == QLatin1String("MergeFile" )) { |
569 | if ((e.attribute(QStringLiteral("type" )) == QLatin1String("parent" ))) { |
570 | // Ignore e.text(), as per the standard. We'll just look up the parent (more global) xml file. |
571 | pushDocInfoParent(basePath: e.attribute(QStringLiteral("__BasePath" )), baseDir: e.attribute(QStringLiteral("__BaseDir" ))); |
572 | } else { |
573 | pushDocInfo(fileName: e.text(), baseDir: e.attribute(QStringLiteral("__BaseDir" ))); |
574 | } |
575 | |
576 | if (!m_docInfo.path.isEmpty()) { |
577 | mergeFile(parent&: docElem, mergeHere: n); |
578 | } |
579 | popDocInfo(); |
580 | |
581 | QDomNode last = n; |
582 | n = n.nextSibling(); |
583 | docElem.removeChild(oldChild: last); // Remove the MergeFile node |
584 | continue; |
585 | } else if (e.tagName() == QLatin1String("MergeDir" )) { |
586 | const QString dir = absoluteDir(dir: e.text(), baseDir: e.attribute(QStringLiteral("__BaseDir" )), keepRelativeToCfg: true); |
587 | Q_ASSERT(dir.endsWith(QLatin1Char('/'))); |
588 | |
589 | const bool relative = QDir::isRelativePath(path: dir); |
590 | const QStringList dirs = |
591 | QStandardPaths::locateAll(type: QStandardPaths::GenericConfigLocation, fileName: QLatin1String("menus/" ) + dir, options: QStandardPaths::LocateDirectory); |
592 | for (const QString & : dirs) { |
593 | registerDirectory(directory: menuDir); |
594 | } |
595 | |
596 | QStringList fileList; |
597 | for (const QString & : dirs) { |
598 | const QStringList fileNames = QDir(menuDir).entryList(nameFilters: QStringList() << QStringLiteral("*.menu" )); |
599 | for (const QString &file : fileNames) { |
600 | const QString fileToAdd = relative ? dir + file : menuDir + file; |
601 | if (!fileList.contains(str: fileToAdd)) { |
602 | fileList.append(t: fileToAdd); |
603 | } |
604 | } |
605 | } |
606 | |
607 | for (const QString &file : std::as_const(t&: fileList)) { |
608 | pushDocInfo(fileName: file); |
609 | mergeFile(parent&: docElem, mergeHere: n); |
610 | popDocInfo(); |
611 | } |
612 | |
613 | QDomNode last = n; |
614 | n = n.nextSibling(); |
615 | docElem.removeChild(oldChild: last); // Remove the MergeDir node |
616 | |
617 | continue; |
618 | } else if (e.tagName() == QLatin1String("Name" )) { |
619 | name = e.text(); |
620 | } else if (e.tagName() == QLatin1String("DefaultLayout" )) { |
621 | if (!defaultLayoutNode.isNull()) { |
622 | docElem.removeChild(oldChild: defaultLayoutNode); |
623 | } |
624 | defaultLayoutNode = e; |
625 | } else if (e.tagName() == QLatin1String("Layout" )) { |
626 | if (!layoutNode.isNull()) { |
627 | docElem.removeChild(oldChild: layoutNode); |
628 | } |
629 | layoutNode = e; |
630 | } |
631 | n = n.nextSibling(); |
632 | } |
633 | } |
634 | |
635 | static QString makeRelative(const QString &dir) |
636 | { |
637 | const QString canonical = QDir(dir).canonicalPath(); |
638 | const auto list = QStandardPaths::locateAll(type: QStandardPaths::GenericConfigLocation, QStringLiteral("menus" ), options: QStandardPaths::LocateDirectory); |
639 | for (const QString &base : list) { |
640 | if (canonical.startsWith(s: base)) { |
641 | return canonical.mid(position: base.length() + 1); |
642 | } |
643 | } |
644 | return dir; |
645 | } |
646 | |
647 | void VFolderMenu::(const QString &fileName, const QString &baseDir) |
648 | { |
649 | m_docInfoStack.push(t: m_docInfo); |
650 | if (!baseDir.isEmpty()) { |
651 | if (!QDir::isRelativePath(path: baseDir)) { |
652 | m_docInfo.baseDir = makeRelative(dir: baseDir); |
653 | } else { |
654 | m_docInfo.baseDir = baseDir; |
655 | } |
656 | } |
657 | |
658 | QString baseName = fileName; |
659 | if (!QDir::isRelativePath(path: baseName)) { |
660 | registerFile(file: baseName); |
661 | } else { |
662 | baseName = m_docInfo.baseDir + baseName; |
663 | } |
664 | |
665 | m_docInfo.path = locateMenuFile(fileName); |
666 | if (m_docInfo.path.isEmpty()) { |
667 | m_docInfo.baseDir.clear(); |
668 | m_docInfo.baseName.clear(); |
669 | qCDebug(SYCOCA) << "Menu" << fileName << "not found." ; |
670 | return; |
671 | } |
672 | qCDebug(SYCOCA) << "Found menu file" << m_docInfo.path; |
673 | int i; |
674 | i = baseName.lastIndexOf(c: QLatin1Char('/')); |
675 | if (i > 0) { |
676 | m_docInfo.baseDir = baseName.left(n: i + 1); |
677 | m_docInfo.baseName = baseName.mid(position: i + 1, n: baseName.length() - i - 6); |
678 | } else { |
679 | m_docInfo.baseDir.clear(); |
680 | m_docInfo.baseName = baseName.left(n: baseName.length() - 5); |
681 | } |
682 | } |
683 | |
684 | void VFolderMenu::(const QString &basePath, const QString &baseDir) |
685 | { |
686 | m_docInfoStack.push(t: m_docInfo); |
687 | |
688 | m_docInfo.baseDir = baseDir; |
689 | |
690 | QString fileName = basePath.mid(position: basePath.lastIndexOf(c: QLatin1Char('/')) + 1); |
691 | m_docInfo.baseName = fileName.left(n: fileName.length() - 5); // without ".menu" |
692 | QString baseName = QDir::cleanPath(path: m_docInfo.baseDir + fileName); |
693 | |
694 | QStringList result = QStandardPaths::locateAll(type: QStandardPaths::GenericConfigLocation, fileName: QLatin1String("menus/" ) + baseName); |
695 | |
696 | // Remove anything "more local" than basePath. |
697 | while (!result.isEmpty() && (result.at(i: 0) != basePath)) { |
698 | result.removeFirst(); |
699 | } |
700 | |
701 | if (result.count() <= 1) { |
702 | m_docInfo.path.clear(); // No parent found |
703 | return; |
704 | } |
705 | // Now result.at(0) == basePath, take the next one, i.e. the one in the parent dir. |
706 | m_docInfo.path = result.at(i: 1); |
707 | } |
708 | |
709 | void VFolderMenu::() |
710 | { |
711 | m_docInfo = m_docInfoStack.pop(); |
712 | } |
713 | |
714 | QString VFolderMenu::(const QString &fileName) |
715 | { |
716 | if (!QDir::isRelativePath(path: fileName)) { |
717 | if (QFile::exists(fileName)) { |
718 | return fileName; |
719 | } |
720 | return QString(); |
721 | } |
722 | |
723 | QString result; |
724 | |
725 | QString = QString::fromLocal8Bit(ba: qgetenv(varName: "XDG_MENU_PREFIX" )); |
726 | if (!xdgMenuPrefix.isEmpty()) { |
727 | QFileInfo fileInfo(fileName); |
728 | |
729 | QString fileNameOnly = fileInfo.fileName(); |
730 | if (!fileNameOnly.startsWith(s: xdgMenuPrefix)) { |
731 | fileNameOnly = xdgMenuPrefix + fileNameOnly; |
732 | } |
733 | |
734 | QString baseName = QDir::cleanPath(path: m_docInfo.baseDir + fileInfo.path() + QLatin1Char('/') + fileNameOnly); |
735 | result = QStandardPaths::locate(type: QStandardPaths::GenericConfigLocation, fileName: QLatin1String("menus/" ) + baseName); |
736 | } |
737 | |
738 | if (result.isEmpty()) { |
739 | QString baseName = QDir::cleanPath(path: m_docInfo.baseDir + fileName); |
740 | result = QStandardPaths::locate(type: QStandardPaths::GenericConfigLocation, fileName: QLatin1String("menus/" ) + baseName); |
741 | } |
742 | |
743 | return result; |
744 | } |
745 | |
746 | QString VFolderMenu::(const QString &fileName) |
747 | { |
748 | if (fileName.isEmpty()) { |
749 | return QString(); |
750 | } |
751 | |
752 | if (!QDir::isRelativePath(path: fileName)) { |
753 | if (QFile::exists(fileName)) { |
754 | return fileName; |
755 | } |
756 | return QString(); |
757 | } |
758 | |
759 | // First location in the list wins |
760 | for (QStringList::ConstIterator it = m_directoryDirs.constBegin(); it != m_directoryDirs.constEnd(); ++it) { |
761 | QString tmp = (*it) + fileName; |
762 | if (QFile::exists(fileName: tmp)) { |
763 | return tmp; |
764 | } |
765 | } |
766 | |
767 | return QString(); |
768 | } |
769 | |
770 | void VFolderMenu::() |
771 | { |
772 | m_defaultAppDirs = QStandardPaths::standardLocations(type: QStandardPaths::ApplicationsLocation); |
773 | m_defaultDirectoryDirs = |
774 | QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, QStringLiteral("desktop-directories" ), options: QStandardPaths::LocateDirectory); |
775 | } |
776 | |
777 | void VFolderMenu::(const QString &fileName) |
778 | { |
779 | m_defaultMergeDirs.clear(); |
780 | |
781 | if (!fileName.endsWith(s: QLatin1String(".menu" ))) { |
782 | return; |
783 | } |
784 | |
785 | pushDocInfo(fileName); |
786 | m_defaultMergeDirs << QStringLiteral("applications-merged/" ); |
787 | m_doc = loadDoc(); |
788 | popDocInfo(); |
789 | |
790 | if (m_doc.isNull()) { |
791 | if (m_docInfo.path.isEmpty()) { |
792 | qCritical() << fileName << " not found in " << m_allDirectories; |
793 | } else { |
794 | qCWarning(SYCOCA) << "Load error (" << m_docInfo.path << ")" ; |
795 | } |
796 | return; |
797 | } |
798 | |
799 | QDomElement e = m_doc.documentElement(); |
800 | QString name; |
801 | mergeMenus(docElem&: e, name); |
802 | } |
803 | |
804 | void VFolderMenu::(QDomElement &domElem, QHash<QString, KService::Ptr> &items) |
805 | { |
806 | if (domElem.tagName() == QLatin1String("And" )) { |
807 | QDomNode n = domElem.firstChild(); |
808 | // Look for the first child element |
809 | while (!n.isNull()) { // loop in case of comments |
810 | QDomElement e = n.toElement(); |
811 | n = n.nextSibling(); |
812 | if (!e.isNull()) { |
813 | processCondition(domElem&: e, items); |
814 | break; // we only want the first one |
815 | } |
816 | } |
817 | |
818 | QHash<QString, KService::Ptr> andItems; |
819 | while (!n.isNull()) { |
820 | QDomElement e = n.toElement(); |
821 | if (e.tagName() == QLatin1String("Not" )) { |
822 | // Special handling for "and not" |
823 | QDomNode n2 = e.firstChild(); |
824 | while (!n2.isNull()) { |
825 | QDomElement e2 = n2.toElement(); |
826 | andItems.clear(); |
827 | processCondition(domElem&: e2, items&: andItems); |
828 | excludeItems(items1&: items, items2: andItems); |
829 | n2 = n2.nextSibling(); |
830 | } |
831 | } else { |
832 | andItems.clear(); |
833 | processCondition(domElem&: e, items&: andItems); |
834 | matchItems(items1&: items, items2: andItems); |
835 | } |
836 | n = n.nextSibling(); |
837 | } |
838 | } else if (domElem.tagName() == QLatin1String("Or" )) { |
839 | QDomNode n = domElem.firstChild(); |
840 | // Look for the first child element |
841 | while (!n.isNull()) { // loop in case of comments |
842 | QDomElement e = n.toElement(); |
843 | n = n.nextSibling(); |
844 | if (!e.isNull()) { |
845 | processCondition(domElem&: e, items); |
846 | break; // we only want the first one |
847 | } |
848 | } |
849 | |
850 | QHash<QString, KService::Ptr> orItems; |
851 | while (!n.isNull()) { |
852 | QDomElement e = n.toElement(); |
853 | if (!e.isNull()) { |
854 | orItems.clear(); |
855 | processCondition(domElem&: e, items&: orItems); |
856 | includeItems(items1&: items, items2: orItems); |
857 | } |
858 | n = n.nextSibling(); |
859 | } |
860 | } else if (domElem.tagName() == QLatin1String("Not" )) { |
861 | FOR_ALL_APPLICATIONS(it) |
862 | { |
863 | KService::Ptr s = it.value(); |
864 | items.insert(key: s->menuId(), value: s); |
865 | } |
866 | FOR_ALL_APPLICATIONS_END |
867 | |
868 | QHash<QString, KService::Ptr> notItems; |
869 | QDomNode n = domElem.firstChild(); |
870 | while (!n.isNull()) { |
871 | QDomElement e = n.toElement(); |
872 | if (!e.isNull()) { |
873 | notItems.clear(); |
874 | processCondition(domElem&: e, items&: notItems); |
875 | excludeItems(items1&: items, items2: notItems); |
876 | } |
877 | n = n.nextSibling(); |
878 | } |
879 | } else if (domElem.tagName() == QLatin1String("Category" )) { |
880 | FOR_CATEGORY(domElem.text(), it) |
881 | { |
882 | KService::Ptr s = *it; |
883 | items.insert(key: s->menuId(), value: s); |
884 | } |
885 | FOR_CATEGORY_END |
886 | } else if (domElem.tagName() == QLatin1String("All" )) { |
887 | FOR_ALL_APPLICATIONS(it) |
888 | { |
889 | KService::Ptr s = it.value(); |
890 | items.insert(key: s->menuId(), value: s); |
891 | } |
892 | FOR_ALL_APPLICATIONS_END |
893 | } else if (domElem.tagName() == QLatin1String("Filename" )) { |
894 | const QString filename = domElem.text(); |
895 | // qCDebug(SYCOCA) << "Adding file" << filename; |
896 | KService::Ptr s = findApplication(relPath: filename); |
897 | if (s) { |
898 | items.insert(key: filename, value: s); |
899 | } |
900 | } |
901 | } |
902 | |
903 | void VFolderMenu::(const QString &dir, const QString &prefix) |
904 | { |
905 | qCDebug(SYCOCA) << "Looking up applications under" << dir; |
906 | |
907 | QDirIterator it(dir); |
908 | while (it.hasNext()) { |
909 | it.next(); |
910 | const QFileInfo fi = it.fileInfo(); |
911 | const QString fn = fi.fileName(); |
912 | if (fi.isDir() && !fi.isSymLink() && !fi.isBundle()) { // same check as in ksycocautils_p.h |
913 | if (fn == QLatin1String("." ) || fn == QLatin1String(".." )) { |
914 | continue; |
915 | } |
916 | loadApplications(dir: fi.filePath(), prefix: prefix + fn + QLatin1Char('-')); |
917 | continue; |
918 | } |
919 | if (fi.isFile()) { |
920 | if (!fn.endsWith(s: QLatin1String(".desktop" ))) { |
921 | continue; |
922 | } |
923 | KService::Ptr service = m_kbuildsycocaInterface->createService(path: fi.absoluteFilePath()); |
924 | #ifndef NDEBUG |
925 | if (fn.contains(s: QLatin1String("fake" ))) { |
926 | qCDebug(SYCOCA) << "createService" << fi.absoluteFilePath() << "returned" << (service ? "valid service" : "NULL SERVICE" ); |
927 | } |
928 | #endif |
929 | if (service) { |
930 | addApplication(id: prefix + fn, service); |
931 | } |
932 | } |
933 | } |
934 | } |
935 | |
936 | void VFolderMenu::(const QString &dir, const QString &relDir, const QString &prefix) |
937 | { |
938 | // qCDebug(SYCOCA).nospace() << "processLegacyDir(" << dir << ", " << relDir << ", " << prefix << ")"; |
939 | |
940 | QHash<QString, KService::Ptr> items; |
941 | QDirIterator it(dir); |
942 | while (it.hasNext()) { |
943 | it.next(); |
944 | const QFileInfo fi = it.fileInfo(); |
945 | const QString fn = fi.fileName(); |
946 | if (fi.isDir()) { |
947 | if (fn == QLatin1String("." ) || fn == QLatin1String(".." )) { |
948 | continue; |
949 | } |
950 | SubMenu * = m_currentMenu; |
951 | |
952 | m_currentMenu = new SubMenu; |
953 | m_currentMenu->name = fn; |
954 | m_currentMenu->directoryFile = fi.absoluteFilePath() + QLatin1String("/.directory" ); |
955 | |
956 | parentMenu->subMenus.append(t: m_currentMenu); |
957 | |
958 | processLegacyDir(dir: fi.filePath(), relDir: relDir + fn + QLatin1Char('/'), prefix); |
959 | m_currentMenu = parentMenu; |
960 | continue; |
961 | } |
962 | if (fi.isFile() /*&& !fi.isSymLink() ?? */) { |
963 | if (!fn.endsWith(s: QLatin1String(".desktop" ))) { |
964 | continue; |
965 | } |
966 | KService::Ptr service = m_kbuildsycocaInterface->createService(path: fi.absoluteFilePath()); |
967 | if (service) { |
968 | const QString id = prefix + fn; |
969 | |
970 | // TODO: Add legacy category |
971 | addApplication(id, service); |
972 | items.insert(key: service->menuId(), value: service); |
973 | |
974 | if (service->categories().isEmpty()) { |
975 | m_currentMenu->items.insert(key: id, value: service); |
976 | } |
977 | } |
978 | } |
979 | } |
980 | markUsedApplications(items); |
981 | } |
982 | |
983 | void VFolderMenu::(QDomElement &docElem, int pass) |
984 | { |
985 | SubMenu * = m_currentMenu; |
986 | int oldDirectoryDirsCount = m_directoryDirs.count(); |
987 | |
988 | QString name; |
989 | QString directoryFile; |
990 | bool onlyUnallocated = false; |
991 | bool isDeleted = false; |
992 | QDomElement defaultLayoutNode; |
993 | QDomElement layoutNode; |
994 | |
995 | QDomElement query; |
996 | QDomNode n = docElem.firstChild(); |
997 | while (!n.isNull()) { |
998 | QDomElement e = n.toElement(); // try to convert the node to an element. |
999 | if (e.tagName() == QLatin1String("Name" )) { |
1000 | name = e.text(); |
1001 | } else if (e.tagName() == QLatin1String("Directory" )) { |
1002 | directoryFile = e.text(); |
1003 | } else if (e.tagName() == QLatin1String("DirectoryDir" )) { |
1004 | QString dir = absoluteDir(dir: e.text(), baseDir: e.attribute(QStringLiteral("__BaseDir" ))); |
1005 | |
1006 | m_directoryDirs.prepend(t: 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 * : std::as_const(t&: 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(t: 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(fileName: 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 * : std::as_const(t&: 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(dir: e.text(), baseDir: e.attribute(QStringLiteral("__BaseDir" ))); |
1083 | |
1084 | registerDirectory(directory: dir); |
1085 | |
1086 | loadApplications(dir, prefix: QString()); |
1087 | } else if (e.tagName() == QLatin1String("LegacyDir" )) { |
1088 | createAppsInfo(); |
1089 | QString dir = absoluteDir(dir: e.text(), baseDir: e.attribute(QStringLiteral("__BaseDir" ))); |
1090 | |
1091 | QString prefix = e.attributes().namedItem(QStringLiteral("prefix" )).toAttr().value(); |
1092 | |
1093 | SubMenu * = m_currentMenu; |
1094 | m_currentMenu = new SubMenu; |
1095 | |
1096 | registerDirectory(directory: dir); |
1097 | |
1098 | processLegacyDir(dir, relDir: QString(), prefix); |
1099 | |
1100 | m_legacyNodes.insert(key: dir, value: 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(domElem&: e2, items); |
1122 | if (m_track) { |
1123 | track(menuId: m_trackId, menuName: m_currentMenu->name, includeList: m_currentMenu->items, excludeList: m_currentMenu->excludeItems, itemList: items, QStringLiteral("Before <Include>" )); |
1124 | } |
1125 | includeItems(items1&: m_currentMenu->items, items2: items); |
1126 | excludeItems(items1&: m_currentMenu->excludeItems, items2: items); |
1127 | markUsedApplications(items); |
1128 | |
1129 | if (m_track) { |
1130 | track(menuId: m_trackId, menuName: m_currentMenu->name, includeList: m_currentMenu->items, excludeList: m_currentMenu->excludeItems, itemList: 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(domElem&: e2, items); |
1145 | if (m_track) { |
1146 | track(menuId: m_trackId, menuName: m_currentMenu->name, includeList: m_currentMenu->items, excludeList: m_currentMenu->excludeItems, itemList: items, QStringLiteral("Before <Exclude>" )); |
1147 | } |
1148 | excludeItems(items1&: m_currentMenu->items, items2: items); |
1149 | includeItems(items1&: m_currentMenu->excludeItems, items2: items); |
1150 | if (m_track) { |
1151 | track(menuId: m_trackId, menuName: m_currentMenu->name, includeList: m_currentMenu->items, excludeList: m_currentMenu->excludeItems, itemList: 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(docElem&: 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(dir: e.text(), baseDir: e.attribute(QStringLiteral("__BaseDir" ))); |
1175 | SubMenu * = m_legacyNodes[dir]; |
1176 | if (legacyMenu) { |
1177 | mergeMenu(menu1: m_currentMenu, menu2: 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 * = takeSubMenu(parentMenu: m_currentMenu, menuName: orig); |
1205 | if (menu) { |
1206 | insertSubMenu(parentMenu: m_currentMenu, menuName: dest, newMenu: menu, reversePriority: 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(name: SHOW_EMPTY)) { |
1229 | QString str = e.attribute(name: 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(name: INLINE)) { |
1240 | QString str = e.attribute(name: 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: &ok); |
1252 | if (ok) { |
1253 | option += QStringLiteral("IL[%1] " ).arg(a: 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::(const QDomElement &docElem) const |
1283 | { |
1284 | QStringList layout; |
1285 | |
1286 | QString optionDefaultLayout; |
1287 | if (docElem.tagName() == QLatin1String("DefaultLayout" )) { |
1288 | optionDefaultLayout = parseAttribute(e: docElem); |
1289 | } |
1290 | if (!optionDefaultLayout.isEmpty()) { |
1291 | layout.append(t: 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(t: e.text()); |
1302 | } else if (e.tagName() == QLatin1String("Menuname" )) { |
1303 | layout.append(t: QLatin1Char('/') + e.text()); |
1304 | QString option = parseAttribute(e); |
1305 | if (!option.isEmpty()) { |
1306 | layout.append(t: 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::(VFolderMenu::SubMenu *, QStringList defaultLayout) // krazy:exclude=passbyvalue |
1333 | { |
1334 | if (!menu->defaultLayoutNode.isNull()) { |
1335 | defaultLayout = parseLayoutNode(docElem: menu->defaultLayoutNode); |
1336 | } |
1337 | |
1338 | if (menu->layoutNode.isNull()) { |
1339 | menu->layoutList = defaultLayout; |
1340 | } else { |
1341 | menu->layoutList = parseLayoutNode(docElem: menu->layoutNode); |
1342 | if (menu->layoutList.isEmpty()) { |
1343 | menu->layoutList = defaultLayout; |
1344 | } |
1345 | } |
1346 | |
1347 | for (VFolderMenu::SubMenu * : std::as_const(t&: menu->subMenus)) { |
1348 | layoutMenu(menu: subMenu, defaultLayout); |
1349 | } |
1350 | } |
1351 | |
1352 | void VFolderMenu::(const QHash<QString, KService::Ptr> &items) |
1353 | { |
1354 | for (const KService::Ptr &p : items) { |
1355 | m_usedAppsDict.insert(value: p->menuId()); |
1356 | } |
1357 | } |
1358 | |
1359 | VFolderMenu::SubMenu *VFolderMenu::(const QString &file) |
1360 | { |
1361 | m_appsInfo = nullptr; |
1362 | |
1363 | const QStringList dirs = QStandardPaths::locateAll(type: QStandardPaths::GenericConfigLocation, QStringLiteral("menus" ), options: QStandardPaths::LocateDirectory); |
1364 | for (QStringList::ConstIterator it = dirs.begin(); it != dirs.end(); ++it) { |
1365 | registerDirectory(directory: *it); |
1366 | } |
1367 | |
1368 | loadMenu(fileName: 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(unusedOnly: 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(unusedOnly: true /* unusedOnly */); |
1391 | break; |
1392 | case 2: { |
1393 | QStringList defaultLayout; |
1394 | defaultLayout << QStringLiteral(":M" ); // Sub-Menus |
1395 | defaultLayout << QStringLiteral(":F" ); // Individual entries |
1396 | layoutMenu(menu: m_rootMenu, defaultLayout); |
1397 | break; |
1398 | } |
1399 | default: |
1400 | break; |
1401 | } |
1402 | } |
1403 | |
1404 | return m_rootMenu; |
1405 | } |
1406 | |
1407 | void VFolderMenu::(const QString &id) |
1408 | { |
1409 | m_track = !id.isEmpty(); |
1410 | m_trackId = id; |
1411 | } |
1412 | |
1413 | #include "moc_vfolder_menu_p.cpp" |
1414 | |