1/* This file is part of the KDE libraries
2 * Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License version 2 as published by the Free Software Foundation;
7 *
8 * This library is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Library General Public License for more details.
12 *
13 * You should have received a copy of the GNU Library General Public License
14 * along with this library; see the file COPYING.LIB. If not, write to
15 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 * Boston, MA 02110-1301, USA.
17 **/
18
19#include "vfolder_menu.h"
20#include "kbuildservicefactory.h"
21#include "kbuildsycocainterface.h"
22
23#include <sys/types.h>
24#include <sys/stat.h>
25#include <unistd.h>
26#include <dirent.h>
27#include <config.h>
28
29#include <kdebug.h>
30#include <kglobal.h>
31#include <kstandarddirs.h>
32#include <kservice.h>
33#include <kde_file.h>
34
35#include <QtCore/QMap>
36#include <QtCore/QFile>
37#include <QtCore/QDir>
38#include <QtCore/QRegExp>
39#include <QtCore/QDirIterator>
40
41static void foldNode(QDomElement &docElem, QDomElement &e, QMap<QString,QDomElement> &dupeList, QString s=QString()) //krazy:exclude=passbyvalue
42{
43 if (s.isEmpty())
44 s = e.text();
45 QMap<QString,QDomElement>::iterator it = dupeList.find(s);
46 if (it != dupeList.end())
47 {
48 kDebug(7021) << e.tagName() << "and" << s << "requires combining!";
49
50 docElem.removeChild(*it);
51 dupeList.erase(it);
52 }
53 dupeList.insert(s, e);
54}
55
56static void replaceNode(QDomElement &docElem, QDomNode &n, const QStringList &list, const QString &tag)
57{
58 for(QStringList::ConstIterator it = list.begin();
59 it != list.end(); ++it)
60 {
61 QDomElement e = docElem.ownerDocument().createElement(tag);
62 QDomText txt = docElem.ownerDocument().createTextNode(*it);
63 e.appendChild(txt);
64 docElem.insertAfter(e, n);
65 }
66
67 QDomNode next = n.nextSibling();
68 docElem.removeChild(n);
69 n = next;
70// kDebug(7021) << "Next tag = " << n.toElement().tagName();
71}
72
73void VFolderMenu::registerFile(const QString &file)
74{
75 int i = file.lastIndexOf('/');
76 if (i < 0)
77 return;
78
79 QString dir = file.left(i+1); // Include trailing '/'
80 registerDirectory(dir);
81}
82
83void VFolderMenu::registerDirectory(const QString &directory)
84{
85 m_allDirectories.append(directory);
86}
87
88QStringList VFolderMenu::allDirectories()
89{
90 if (m_allDirectories.isEmpty())
91 return m_allDirectories;
92 m_allDirectories.sort();
93
94 QStringList::Iterator it = m_allDirectories.begin();
95 QString previous = *it++;
96 for(;it != m_allDirectories.end();)
97 {
98#ifndef Q_OS_WIN
99 if ((*it).startsWith(previous))
100#else
101 if ((*it).startsWith(previous, Qt::CaseInsensitive))
102#endif
103 {
104 it = m_allDirectories.erase(it);
105 }
106 else
107 {
108 previous = *it;
109 ++it;
110 }
111 }
112 return m_allDirectories;
113}
114
115static void
116track(const QString &menuId, const QString &menuName, const QHash<QString,KService::Ptr>& includeList, const QHash<QString,KService::Ptr>& excludeList, const QHash<QString,KService::Ptr>& itemList, const QString &comment)
117{
118 if (itemList.contains(menuId))
119 printf("%s: %s INCL %d EXCL %d\n", qPrintable(menuName), qPrintable(comment), includeList.contains(menuId) ? 1 : 0, excludeList.contains(menuId) ? 1 : 0);
120}
121
122void
123VFolderMenu::includeItems(QHash<QString,KService::Ptr>& items1, const QHash<QString,KService::Ptr>& items2)
124{
125 foreach (const KService::Ptr &p, items2) {
126 items1.insert(p->menuId(), p);
127 }
128}
129
130void
131VFolderMenu::matchItems(QHash<QString,KService::Ptr>& items1, const QHash<QString,KService::Ptr>& items2)
132{
133 foreach (const KService::Ptr &p, items1)
134 {
135 QString id = p->menuId();
136 if (!items2.contains(id))
137 items1.remove(id);
138 }
139}
140
141void
142VFolderMenu::excludeItems(QHash<QString,KService::Ptr>& items1, const QHash<QString,KService::Ptr>& items2)
143{
144 foreach (const KService::Ptr &p, items2)
145 items1.remove(p->menuId());
146}
147
148VFolderMenu::SubMenu*
149VFolderMenu::takeSubMenu(SubMenu *parentMenu, const QString &menuName)
150{
151 const int i = menuName.indexOf('/');
152 const QString s1 = i > 0 ? menuName.left(i) : menuName;
153 const QString s2 = menuName.mid(i+1);
154
155 // Look up menu
156 for (QList<SubMenu*>::Iterator it = parentMenu->subMenus.begin(); it != parentMenu->subMenus.end(); ++it)
157 {
158 SubMenu* menu = *it;
159 if (menu->name == s1)
160 {
161 if (i == -1)
162 {
163 // Take it out
164 parentMenu->subMenus.erase(it);
165 return menu;
166 }
167 else
168 {
169 return takeSubMenu(menu, s2);
170 }
171 }
172 }
173 return 0; // Not found
174}
175
176void
177VFolderMenu::mergeMenu(SubMenu *menu1, SubMenu *menu2, bool reversePriority)
178{
179 if (m_track)
180 {
181 track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QString("Before MenuMerge w. %1 (incl)").arg(menu2->name));
182 track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QString("Before MenuMerge w. %1 (excl)").arg(menu2->name));
183 }
184 if (reversePriority)
185 {
186 // Merge menu1 with menu2, menu1 takes precedent
187 excludeItems(menu2->items, menu1->excludeItems);
188 includeItems(menu1->items, menu2->items);
189 excludeItems(menu2->excludeItems, menu1->items);
190 includeItems(menu1->excludeItems, menu2->excludeItems);
191 }
192 else
193 {
194 // Merge menu1 with menu2, menu2 takes precedent
195 excludeItems(menu1->items, menu2->excludeItems);
196 includeItems(menu1->items, menu2->items);
197 includeItems(menu1->excludeItems, menu2->excludeItems);
198 menu1->isDeleted = menu2->isDeleted;
199 }
200 while (!menu2->subMenus.isEmpty())
201 {
202 SubMenu *subMenu = menu2->subMenus.takeFirst();
203 insertSubMenu(menu1, subMenu->name, subMenu, reversePriority);
204 }
205
206 if (reversePriority)
207 {
208 // Merge menu1 with menu2, menu1 takes precedent
209 if (menu1->directoryFile.isEmpty())
210 menu1->directoryFile = menu2->directoryFile;
211 if (menu1->defaultLayoutNode.isNull())
212 menu1->defaultLayoutNode = menu2->defaultLayoutNode;
213 if (menu1->layoutNode.isNull())
214 menu1->layoutNode = menu2->layoutNode;
215 }
216 else
217 {
218 // Merge menu1 with menu2, menu2 takes precedent
219 if (!menu2->directoryFile.isEmpty())
220 menu1->directoryFile = menu2->directoryFile;
221 if (!menu2->defaultLayoutNode.isNull())
222 menu1->defaultLayoutNode = menu2->defaultLayoutNode;
223 if (!menu2->layoutNode.isNull())
224 menu1->layoutNode = menu2->layoutNode;
225 }
226
227 if (m_track)
228 {
229 track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->items, QString("After MenuMerge w. %1 (incl)").arg(menu2->name));
230 track(m_trackId, menu1->name, menu1->items, menu1->excludeItems, menu2->excludeItems, QString("After MenuMerge w. %1 (excl)").arg(menu2->name));
231 }
232
233 delete menu2;
234}
235
236void
237VFolderMenu::insertSubMenu(SubMenu *parentMenu, const QString &menuName, SubMenu *newMenu, bool reversePriority)
238{
239 const int i = menuName.indexOf('/');
240 const QString s1 = menuName.left(i);
241 const QString s2 = menuName.mid(i+1);
242
243 // Look up menu
244 foreach (SubMenu *menu, parentMenu->subMenus)
245 {
246 if (menu->name == s1)
247 {
248 if (i == -1)
249 {
250 mergeMenu(menu, newMenu, reversePriority);
251 return;
252 }
253 else
254 {
255 insertSubMenu(menu, s2, newMenu, reversePriority);
256 return;
257 }
258 }
259 }
260 if (i == -1)
261 {
262 // Add it here
263 newMenu->name = menuName;
264 parentMenu->subMenus.append(newMenu);
265 }
266 else
267 {
268 SubMenu *menu = new SubMenu;
269 menu->name = s1;
270 parentMenu->subMenus.append(menu);
271 insertSubMenu(menu, s2, newMenu);
272 }
273}
274
275void
276VFolderMenu::insertService(SubMenu *parentMenu, const QString &name, KService::Ptr newService)
277{
278 const int i = name.indexOf('/');
279
280 if (i == -1)
281 {
282 // Add it here
283 parentMenu->items.insert(newService->menuId(), newService);
284 return;
285 }
286
287 QString s1 = name.left(i);
288 QString s2 = name.mid(i+1);
289
290 // Look up menu
291 foreach (SubMenu *menu, parentMenu->subMenus)
292 {
293 if (menu->name == s1)
294 {
295 insertService(menu, s2, newService);
296 return;
297 }
298 }
299
300 SubMenu *menu = new SubMenu;
301 menu->name = s1;
302 parentMenu->subMenus.append(menu);
303 insertService(menu, s2, newService);
304}
305
306
307VFolderMenu::VFolderMenu(KBuildServiceFactory* serviceFactory, KBuildSycocaInterface* kbuildsycocaInterface)
308 : m_track(false),
309 m_serviceFactory(serviceFactory),
310 m_kbuildsycocaInterface(kbuildsycocaInterface)
311{
312 m_usedAppsDict.reserve(797);
313 m_rootMenu = 0;
314 initDirs();
315}
316
317VFolderMenu::~VFolderMenu()
318{
319 delete m_rootMenu;
320 delete m_appsInfo;
321}
322
323#define FOR_ALL_APPLICATIONS(it) \
324 foreach (AppsInfo *info, m_appsInfoStack) \
325 { \
326 QHashIterator<QString,KService::Ptr> it = info->applications; \
327 while (it.hasNext()) \
328 { \
329 it.next();
330#define FOR_ALL_APPLICATIONS_END } }
331
332#define FOR_CATEGORY(category, it) \
333 foreach (AppsInfo *info, m_appsInfoStack) \
334 { \
335 const KService::List list = info->dictCategories.value(category); \
336 for(KService::List::ConstIterator it = list.constBegin(); \
337 it != list.constEnd(); ++it) \
338 {
339#define FOR_CATEGORY_END } }
340
341KService::Ptr
342VFolderMenu::findApplication(const QString &relPath)
343{
344 foreach(AppsInfo *info, m_appsInfoStack)
345 {
346 if (info->applications.contains(relPath)) {
347 KService::Ptr s = info->applications[relPath];
348 if (s)
349 return s;
350 }
351 }
352 return KService::Ptr();
353}
354
355void
356VFolderMenu::addApplication(const QString &id, KService::Ptr service)
357{
358 service->setMenuId(id);
359 m_appsInfo->applications.insert(id, service); // replaces, if already there
360 m_serviceFactory->addEntry(KSycocaEntry::Ptr::staticCast(service));
361}
362
363void
364VFolderMenu::buildApplicationIndex(bool unusedOnly)
365{
366 foreach (AppsInfo *info, m_appsInfoList)
367 {
368 info->dictCategories.clear();
369 QMutableHashIterator<QString,KService::Ptr> it = info->applications;
370 while (it.hasNext())
371 {
372 KService::Ptr s = it.next().value();
373 if (unusedOnly && m_usedAppsDict.contains(s->menuId()))
374 {
375 // Remove and skip this one
376 it.remove();
377 continue;
378 }
379
380 Q_FOREACH(const QString& cat, s->categories()) {
381 info->dictCategories[cat].append(s); // find or insert entry in hash
382 }
383 }
384 }
385}
386
387void
388VFolderMenu::createAppsInfo()
389{
390 if (m_appsInfo) return;
391
392 m_appsInfo = new AppsInfo;
393 m_appsInfoStack.prepend(m_appsInfo);
394 m_appsInfoList.append(m_appsInfo);
395 m_currentMenu->apps_info = m_appsInfo;
396}
397
398void
399VFolderMenu::loadAppsInfo()
400{
401 m_appsInfo = m_currentMenu->apps_info;
402 if (!m_appsInfo)
403 return; // No appsInfo for this menu
404
405 if (m_appsInfoStack.count() && m_appsInfoStack.first() == m_appsInfo)
406 return; // Already added (By createAppsInfo?)
407
408 m_appsInfoStack.prepend(m_appsInfo); // Add
409}
410
411void
412VFolderMenu::unloadAppsInfo()
413{
414 m_appsInfo = m_currentMenu->apps_info;
415 if (!m_appsInfo)
416 return; // No appsInfo for this menu
417
418 if (m_appsInfoStack.first() != m_appsInfo)
419 {
420 return; // Already removed (huh?)
421 }
422
423 m_appsInfoStack.removeAll(m_appsInfo); // Remove
424 m_appsInfo = 0;
425}
426
427QString
428VFolderMenu::absoluteDir(const QString &_dir, const QString &baseDir, bool keepRelativeToCfg)
429{
430 QString dir = _dir;
431 if (QDir::isRelativePath(dir))
432 {
433 dir = baseDir + dir;
434 }
435 if (!dir.endsWith('/'))
436 dir += '/';
437
438 bool relative = QDir::isRelativePath(dir);
439 if (relative && !keepRelativeToCfg) {
440 relative = false;
441 dir = KGlobal::dirs()->findResource("xdgconf-menu", dir);
442 }
443
444 if (!relative)
445 dir = KGlobal::dirs()->realPath(dir);
446
447 return dir;
448}
449
450static void tagBaseDir(QDomDocument &doc, const QString &tag, const QString &dir)
451{
452 QDomNodeList mergeFileList = doc.elementsByTagName(tag);
453 for(int i = 0; i < (int)mergeFileList.count(); i++)
454 {
455 QDomAttr attr = doc.createAttribute("__BaseDir");
456 attr.setValue(dir);
457 mergeFileList.item(i).toElement().setAttributeNode(attr);
458 }
459}
460
461static void tagBasePath(QDomDocument &doc, const QString &tag, const QString &path)
462{
463 QDomNodeList mergeFileList = doc.elementsByTagName(tag);
464 for(int i = 0; i < (int)mergeFileList.count(); i++)
465 {
466 QDomAttr attr = doc.createAttribute("__BasePath");
467 attr.setValue(path);
468 mergeFileList.item(i).toElement().setAttributeNode(attr);
469 }
470}
471
472QDomDocument
473VFolderMenu::loadDoc()
474{
475 QDomDocument doc;
476 if ( m_docInfo.path.isEmpty() )
477 {
478 return doc;
479 }
480 QFile file( m_docInfo.path );
481 if ( !file.open( QIODevice::ReadOnly ) )
482 {
483 kWarning(7021) << "Could not open " << m_docInfo.path;
484 return doc;
485 }
486 QString errorMsg;
487 int errorRow;
488 int errorCol;
489 if ( !doc.setContent( &file, &errorMsg, &errorRow, &errorCol ) ) {
490 kWarning(7021) << "Parse error in " << m_docInfo.path << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg;
491 file.close();
492 return doc;
493 }
494 file.close();
495
496 tagBaseDir(doc, "MergeFile", m_docInfo.baseDir);
497 tagBasePath(doc, "MergeFile", m_docInfo.path);
498 tagBaseDir(doc, "MergeDir", m_docInfo.baseDir);
499 tagBaseDir(doc, "DirectoryDir", m_docInfo.baseDir);
500 tagBaseDir(doc, "AppDir", m_docInfo.baseDir);
501 tagBaseDir(doc, "LegacyDir", m_docInfo.baseDir);
502
503 return doc;
504}
505
506
507void
508VFolderMenu::mergeFile(QDomElement &parent, const QDomNode &mergeHere)
509{
510kDebug(7021) << "VFolderMenu::mergeFile:" << m_docInfo.path;
511 QDomDocument doc = loadDoc();
512
513 QDomElement docElem = doc.documentElement();
514 QDomNode n = docElem.firstChild();
515 QDomNode last = mergeHere;
516 while( !n.isNull() )
517 {
518 QDomElement e = n.toElement(); // try to convert the node to an element.
519 QDomNode next = n.nextSibling();
520
521 if (e.isNull())
522 {
523 // Skip
524 }
525 // The spec says we must ignore any Name nodes
526 else if (e.tagName() != "Name")
527 {
528 parent.insertAfter(n, last);
529 last = n;
530 }
531
532 docElem.removeChild(n);
533 n = next;
534 }
535}
536
537
538void
539VFolderMenu::mergeMenus(QDomElement &docElem, QString &name)
540{
541 QMap<QString,QDomElement> menuNodes;
542 QMap<QString,QDomElement> directoryNodes;
543 QMap<QString,QDomElement> appDirNodes;
544 QMap<QString,QDomElement> directoryDirNodes;
545 QMap<QString,QDomElement> legacyDirNodes;
546 QDomElement defaultLayoutNode;
547 QDomElement layoutNode;
548
549 QDomNode n = docElem.firstChild();
550 while( !n.isNull() ) {
551 QDomElement e = n.toElement(); // try to convert the node to an element.
552 if( e.isNull() ) {
553// kDebug(7021) << "Empty node";
554 }
555 else if( e.tagName() == "DefaultAppDirs") {
556 // Replace with m_defaultAppDirs
557 replaceNode(docElem, n, m_defaultAppDirs, "AppDir");
558 continue;
559 }
560 else if( e.tagName() == "DefaultDirectoryDirs") {
561 // Replace with m_defaultDirectoryDirs
562 replaceNode(docElem, n, m_defaultDirectoryDirs, "DirectoryDir");
563 continue;
564 }
565 else if( e.tagName() == "DefaultMergeDirs") {
566 // Replace with m_defaultMergeDirs
567 replaceNode(docElem, n, m_defaultMergeDirs, "MergeDir");
568 continue;
569 }
570 else if( e.tagName() == "AppDir") {
571 // Filter out dupes
572 foldNode(docElem, e, appDirNodes);
573 }
574 else if( e.tagName() == "DirectoryDir") {
575 // Filter out dupes
576 foldNode(docElem, e, directoryDirNodes);
577 }
578 else if( e.tagName() == "LegacyDir") {
579 // Filter out dupes
580 foldNode(docElem, e, legacyDirNodes);
581 }
582 else if( e.tagName() == "Directory") {
583 // Filter out dupes
584 foldNode(docElem, e, directoryNodes);
585 }
586 else if( e.tagName() == "Move") {
587 // Filter out dupes
588 QString orig;
589 QDomNode n2 = e.firstChild();
590 while( !n2.isNull() ) {
591 QDomElement e2 = n2.toElement(); // try to convert the node to an element.
592 if( e2.tagName() == "Old")
593 {
594 orig = e2.text();
595 break;
596 }
597 n2 = n2.nextSibling();
598 }
599 foldNode(docElem, e, appDirNodes, orig);
600 }
601 else if( e.tagName() == "Menu") {
602 QString name;
603 mergeMenus(e, name);
604 QMap<QString,QDomElement>::iterator it = menuNodes.find(name);
605 if (it != menuNodes.end())
606 {
607 QDomElement docElem2 = *it;
608 QDomNode n2 = docElem2.firstChild();
609 QDomNode first = e.firstChild();
610 while( !n2.isNull() ) {
611 QDomElement e2 = n2.toElement(); // try to convert the node to an element.
612 QDomNode n3 = n2.nextSibling();
613 e.insertBefore(n2, first);
614 docElem2.removeChild(n2);
615 n2 = n3;
616 }
617 // We still have duplicated Name entries
618 // but we don't care about that
619
620 docElem.removeChild(docElem2);
621 menuNodes.erase(it);
622 }
623 menuNodes.insert(name, e);
624 }
625 else if( e.tagName() == "MergeFile") {
626 if ((e.attribute("type") == "parent"))
627 pushDocInfoParent(e.attribute("__BasePath"), e.attribute("__BaseDir"));
628 else
629 pushDocInfo(e.text(), e.attribute("__BaseDir"));
630
631 if (!m_docInfo.path.isEmpty())
632 mergeFile(docElem, n);
633 popDocInfo();
634
635 QDomNode last = n;
636 n = n.nextSibling();
637 docElem.removeChild(last); // Remove the MergeFile node
638 continue;
639 }
640 else if( e.tagName() == "MergeDir") {
641 QString dir = absoluteDir(e.text(), e.attribute("__BaseDir"), true);
642
643 const QStringList dirs = KGlobal::dirs()->findDirs("xdgconf-menu", dir);
644 for(QStringList::ConstIterator it=dirs.begin();
645 it != dirs.end(); ++it)
646 {
647 registerDirectory(*it);
648 }
649
650 QStringList fileList;
651 if (!QDir::isRelativePath(dir))
652 {
653 // Absolute
654 fileList = KGlobal::dirs()->findAllResources("xdgconf-menu", dir+"*.menu");
655 }
656 else
657 {
658 // Relative
659 (void) KGlobal::dirs()->findAllResources("xdgconf-menu", dir+"*.menu",
660 KStandardDirs::NoDuplicates, fileList);
661 }
662
663 for(QStringList::ConstIterator it=fileList.constBegin();
664 it != fileList.constEnd(); ++it)
665 {
666 pushDocInfo(*it);
667 mergeFile(docElem, n);
668 popDocInfo();
669 }
670
671 QDomNode last = n;
672 n = n.nextSibling();
673 docElem.removeChild(last); // Remove the MergeDir node
674
675 continue;
676 }
677 else if( e.tagName() == "Name") {
678 name = e.text();
679 }
680 else if( e.tagName() == "DefaultLayout") {
681 if (!defaultLayoutNode.isNull())
682 docElem.removeChild(defaultLayoutNode);
683 defaultLayoutNode = e;
684 }
685 else if( e.tagName() == "Layout") {
686 if (!layoutNode.isNull())
687 docElem.removeChild(layoutNode);
688 layoutNode = e;
689 }
690 n = n.nextSibling();
691 }
692}
693
694void
695VFolderMenu::pushDocInfo(const QString &fileName, const QString &baseDir)
696{
697 m_docInfoStack.push(m_docInfo);
698 if (!baseDir.isEmpty())
699 {
700 if (!QDir::isRelativePath(baseDir))
701 m_docInfo.baseDir = KGlobal::dirs()->relativeLocation("xdgconf-menu", baseDir);
702 else
703 m_docInfo.baseDir = baseDir;
704 }
705
706 QString baseName = fileName;
707 if (!QDir::isRelativePath(baseName))
708 registerFile(baseName);
709 else
710 baseName = m_docInfo.baseDir + baseName;
711
712 m_docInfo.path = locateMenuFile(fileName);
713 if (m_docInfo.path.isEmpty())
714 {
715 m_docInfo.baseDir.clear();
716 m_docInfo.baseName.clear();
717 kDebug(7021) << "Menu" << fileName << "not found.";
718 return;
719 }
720 int i;
721 i = baseName.lastIndexOf('/');
722 if (i > 0)
723 {
724 m_docInfo.baseDir = baseName.left(i+1);
725 m_docInfo.baseName = baseName.mid(i+1, baseName.length() - i - 6);
726 }
727 else
728 {
729 m_docInfo.baseDir.clear();
730 m_docInfo.baseName = baseName.left( baseName.length() - 5 );
731 }
732}
733
734void
735VFolderMenu::pushDocInfoParent(const QString &basePath, const QString &baseDir)
736{
737 m_docInfoStack.push(m_docInfo);
738
739 m_docInfo.baseDir = baseDir;
740
741 QString fileName = basePath.mid(basePath.lastIndexOf('/')+1);
742 m_docInfo.baseName = fileName.left( fileName.length() - 5 );
743 QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName);
744
745 QStringList result = KGlobal::dirs()->findAllResources("xdgconf-menu", baseName);
746
747 while( !result.isEmpty() && (result[0] != basePath))
748 result.erase(result.begin());
749
750 if (result.count() <= 1)
751 {
752 m_docInfo.path.clear(); // No parent found
753 return;
754 }
755 m_docInfo.path = result[1];
756}
757
758void
759VFolderMenu::popDocInfo()
760{
761 m_docInfo = m_docInfoStack.pop();
762}
763
764QString
765VFolderMenu::locateMenuFile(const QString &fileName)
766{
767 if (!QDir::isRelativePath(fileName))
768 {
769 if (KStandardDirs::exists(fileName))
770 return fileName;
771 return QString();
772 }
773
774 QString result;
775
776 QString xdgMenuPrefix = QString::fromLocal8Bit(qgetenv("XDG_MENU_PREFIX"));
777 if (!xdgMenuPrefix.isEmpty())
778 {
779 QFileInfo fileInfo(fileName);
780
781 QString fileNameOnly = fileInfo.fileName();
782 if (!fileNameOnly.startsWith(xdgMenuPrefix))
783 fileNameOnly = xdgMenuPrefix + fileNameOnly;
784
785 QString baseName = QDir::cleanPath(m_docInfo.baseDir +
786 fileInfo.path() + '/' + fileNameOnly);
787 result = KStandardDirs::locate("xdgconf-menu", baseName);
788 }
789
790 if (result.isEmpty())
791 {
792 QString baseName = QDir::cleanPath(m_docInfo.baseDir + fileName);
793 result = KStandardDirs::locate("xdgconf-menu", baseName);
794 }
795
796 return result;
797}
798
799QString
800VFolderMenu::locateDirectoryFile(const QString &fileName)
801{
802 if (fileName.isEmpty())
803 return QString();
804
805 if (!QDir::isRelativePath(fileName))
806 {
807 if (KStandardDirs::exists(fileName))
808 return fileName;
809 return QString();
810 }
811
812 // First location in the list wins
813 for(QStringList::ConstIterator it = m_directoryDirs.constBegin();
814 it != m_directoryDirs.constEnd();
815 ++it)
816 {
817 QString tmp = (*it)+fileName;
818 if (KStandardDirs::exists(tmp))
819 return tmp;
820 }
821
822 return QString();
823}
824
825void
826VFolderMenu::initDirs()
827{
828 m_defaultDataDirs = KGlobal::dirs()->kfsstnd_prefixes().split(':', QString::SkipEmptyParts);
829 const QString localDir = m_defaultDataDirs.first();
830 m_defaultDataDirs.removeAll(localDir); // Remove local dir
831
832 m_defaultAppDirs = KGlobal::dirs()->findDirs("xdgdata-apps", QString());
833 m_defaultDirectoryDirs = KGlobal::dirs()->findDirs("xdgdata-dirs", QString());
834 m_defaultLegacyDirs = KGlobal::dirs()->resourceDirs("apps");
835}
836
837void
838VFolderMenu::loadMenu(const QString &fileName)
839{
840 m_defaultMergeDirs.clear();
841
842 if (!fileName.endsWith(QLatin1String(".menu")))
843 return;
844
845 pushDocInfo(fileName);
846 m_defaultMergeDirs << m_docInfo.baseName+"-merged/";
847 m_doc = loadDoc();
848 popDocInfo();
849
850 if (m_doc.isNull())
851 {
852 if (m_docInfo.path.isEmpty())
853 kError(7021) << fileName << " not found in " << m_allDirectories << endl;
854 else
855 kWarning(7021) << "Load error (" << m_docInfo.path << ")";
856 return;
857 }
858
859 QDomElement e = m_doc.documentElement();
860 QString name;
861 mergeMenus(e, name);
862}
863
864void
865VFolderMenu::processCondition(QDomElement &domElem, QHash<QString,KService::Ptr>& items)
866{
867 if (domElem.tagName() == "And")
868 {
869 QDomNode n = domElem.firstChild();
870 // Look for the first child element
871 while (!n.isNull()) // loop in case of comments
872 {
873 QDomElement e = n.toElement();
874 n = n.nextSibling();
875 if ( !e.isNull() ) {
876 processCondition(e, items);
877 break; // we only want the first one
878 }
879 }
880
881 QHash<QString,KService::Ptr> andItems;
882 while( !n.isNull() ) {
883 QDomElement e = n.toElement();
884 if (e.tagName() == "Not")
885 {
886 // Special handling for "and not"
887 QDomNode n2 = e.firstChild();
888 while( !n2.isNull() ) {
889 QDomElement e2 = n2.toElement();
890 andItems.clear();
891 processCondition(e2, andItems);
892 excludeItems(items, andItems);
893 n2 = n2.nextSibling();
894 }
895 }
896 else
897 {
898 andItems.clear();
899 processCondition(e, andItems);
900 matchItems(items, andItems);
901 }
902 n = n.nextSibling();
903 }
904 }
905 else if (domElem.tagName() == "Or")
906 {
907 QDomNode n = domElem.firstChild();
908 // Look for the first child element
909 while (!n.isNull()) // loop in case of comments
910 {
911 QDomElement e = n.toElement();
912 n = n.nextSibling();
913 if ( !e.isNull() ) {
914 processCondition(e, items);
915 break; // we only want the first one
916 }
917 }
918
919 QHash<QString,KService::Ptr> orItems;
920 while( !n.isNull() ) {
921 QDomElement e = n.toElement();
922 if ( !e.isNull() ) {
923 orItems.clear();
924 processCondition(e, orItems);
925 includeItems(items, orItems);
926 }
927 n = n.nextSibling();
928 }
929 }
930 else if (domElem.tagName() == "Not")
931 {
932 FOR_ALL_APPLICATIONS(it)
933 {
934 KService::Ptr s = it.value();
935 items.insert(s->menuId(), s);
936 }
937 FOR_ALL_APPLICATIONS_END
938
939 QHash<QString,KService::Ptr> notItems;
940 QDomNode n = domElem.firstChild();
941 while( !n.isNull() ) {
942 QDomElement e = n.toElement();
943 if ( !e.isNull() ) {
944 notItems.clear();
945 processCondition(e, notItems);
946 excludeItems(items, notItems);
947 }
948 n = n.nextSibling();
949 }
950 }
951 else if (domElem.tagName() == "Category")
952 {
953 FOR_CATEGORY(domElem.text(), it)
954 {
955 KService::Ptr s = *it;
956 items.insert(s->menuId(), s);
957 }
958 FOR_CATEGORY_END
959 }
960 else if (domElem.tagName() == "All")
961 {
962 FOR_ALL_APPLICATIONS(it)
963 {
964 KService::Ptr s = it.value();
965 items.insert(s->menuId(), s);
966 }
967 FOR_ALL_APPLICATIONS_END
968 }
969 else if (domElem.tagName() == "Filename")
970 {
971 const QString filename = domElem.text();
972 //kDebug(7021) << "Adding file" << filename;
973 KService::Ptr s = findApplication(filename);
974 if (s)
975 items.insert(filename, s);
976 }
977}
978
979void
980VFolderMenu::loadApplications(const QString &dir, const QString &prefix)
981{
982 kDebug(7021) << "Looking up applications under" << dir;
983
984 QDirIterator it(dir);
985 while (it.hasNext()) {
986 it.next();
987 const QFileInfo fi = it.fileInfo();
988 const QString fn = fi.fileName();
989 if (fi.isDir()) {
990 if(fn == QLatin1String(".") || fn == QLatin1String(".."))
991 continue;
992 loadApplications(fi.filePath(), prefix + fn + '-');
993 continue;
994 }
995 if (fi.isFile()) {
996 if (!fn.endsWith(QLatin1String(".desktop")))
997 continue;
998 KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath());
999 if (service)
1000 addApplication(prefix + fn, service);
1001 }
1002 }
1003}
1004
1005void
1006VFolderMenu::processKDELegacyDirs()
1007{
1008 kDebug(7021);
1009
1010 QHash<QString,KService::Ptr> items;
1011 QString prefix = "kde4-";
1012
1013 QStringList relFiles;
1014
1015 (void) KGlobal::dirs()->findAllResources( "apps",
1016 QString(),
1017 KStandardDirs::Recursive |
1018 KStandardDirs::NoDuplicates,
1019 relFiles);
1020 for(QStringList::ConstIterator it = relFiles.constBegin();
1021 it != relFiles.constEnd(); ++it)
1022 {
1023 if (!m_forcedLegacyLoad && (*it).endsWith(QLatin1String(".directory")))
1024 {
1025 QString name = *it;
1026 if (!name.endsWith(QLatin1String("/.directory")))
1027 continue; // Probably ".directory", skip it.
1028
1029 name = name.left(name.length()-11);
1030
1031 SubMenu *newMenu = new SubMenu;
1032 newMenu->directoryFile = KStandardDirs::locate("apps", *it);
1033
1034 insertSubMenu(m_currentMenu, name, newMenu);
1035 continue;
1036 }
1037
1038 if ((*it).endsWith(QLatin1String(".desktop")))
1039 {
1040 QString name = *it;
1041 KService::Ptr service = m_kbuildsycocaInterface->createService(name);
1042
1043 if (service && !m_forcedLegacyLoad)
1044 {
1045 QString id = name;
1046 // Strip path from id
1047 int i = id.lastIndexOf('/');
1048 if (i >= 0)
1049 id = id.mid(i+1);
1050
1051 id.prepend(prefix);
1052
1053 // TODO: add Legacy category
1054 addApplication(id, service);
1055 items.insert(service->menuId(), service);
1056 if (service->categories().isEmpty())
1057 insertService(m_currentMenu, name, service);
1058
1059 }
1060 }
1061 }
1062 markUsedApplications(items);
1063 m_legacyLoaded = true;
1064}
1065
1066void
1067VFolderMenu::processLegacyDir(const QString &dir, const QString &relDir, const QString &prefix)
1068{
1069 kDebug(7021).nospace() << "processLegacyDir(" << dir << ", " << relDir << ", " << prefix << ")";
1070
1071 QHash<QString,KService::Ptr> items;
1072 QDirIterator it(dir);
1073 while (it.hasNext()) {
1074 it.next();
1075 const QFileInfo fi = it.fileInfo();
1076 const QString fn = fi.fileName();
1077 if (fi.isDir()) {
1078 if(fn == QLatin1String(".") || fn == QLatin1String(".."))
1079 continue;
1080 SubMenu *parentMenu = m_currentMenu;
1081
1082 m_currentMenu = new SubMenu;
1083 m_currentMenu->name = fn;
1084 m_currentMenu->directoryFile = fi.absoluteFilePath() + "/.directory";
1085
1086 parentMenu->subMenus.append(m_currentMenu);
1087
1088 processLegacyDir(fi.filePath(), relDir + fn + '/', prefix);
1089 m_currentMenu = parentMenu;
1090 continue;
1091 }
1092 if (fi.isFile() /*&& !fi.isSymLink() ?? */) {
1093 if (!fn.endsWith(QLatin1String(".desktop")))
1094 continue;
1095 KService::Ptr service = m_kbuildsycocaInterface->createService(fi.absoluteFilePath());
1096 if (service)
1097 {
1098 const QString id = prefix + fn;
1099
1100 // TODO: Add legacy category
1101 addApplication(id, service);
1102 items.insert(service->menuId(), service);
1103
1104 if (service->categories().isEmpty())
1105 m_currentMenu->items.insert(id, service);
1106 }
1107 }
1108 }
1109 markUsedApplications(items);
1110}
1111
1112
1113
1114void
1115VFolderMenu::processMenu(QDomElement &docElem, int pass)
1116{
1117 SubMenu *parentMenu = m_currentMenu;
1118 int oldDirectoryDirsCount = m_directoryDirs.count();
1119
1120 QString name;
1121 QString directoryFile;
1122 bool onlyUnallocated = false;
1123 bool isDeleted = false;
1124 bool kdeLegacyDirsDone = false;
1125 QDomElement defaultLayoutNode;
1126 QDomElement layoutNode;
1127
1128 QDomElement query;
1129 QDomNode n = docElem.firstChild();
1130 while( !n.isNull() ) {
1131 QDomElement e = n.toElement(); // try to convert the node to an element.
1132 if (e.tagName() == "Name")
1133 {
1134 name = e.text();
1135 }
1136 else if (e.tagName() == "Directory")
1137 {
1138 directoryFile = e.text();
1139 }
1140 else if (e.tagName() == "DirectoryDir")
1141 {
1142 QString dir = absoluteDir(e.text(), e.attribute("__BaseDir"));
1143
1144 m_directoryDirs.prepend(dir);
1145 }
1146 else if (e.tagName() == "OnlyUnallocated")
1147 {
1148 onlyUnallocated = true;
1149 }
1150 else if (e.tagName() == "NotOnlyUnallocated")
1151 {
1152 onlyUnallocated = false;
1153 }
1154 else if (e.tagName() == "Deleted")
1155 {
1156 isDeleted = true;
1157 }
1158 else if (e.tagName() == "NotDeleted")
1159 {
1160 isDeleted = false;
1161 }
1162 else if (e.tagName() == "DefaultLayout")
1163 {
1164 defaultLayoutNode = e;
1165 }
1166 else if (e.tagName() == "Layout")
1167 {
1168 layoutNode = e;
1169 }
1170 n = n.nextSibling();
1171 }
1172
1173 // Setup current menu entry
1174 if (pass == 0)
1175 {
1176 m_currentMenu = 0;
1177 // Look up menu
1178 if (parentMenu)
1179 {
1180 foreach (SubMenu *menu, parentMenu->subMenus)
1181 {
1182 if (menu->name == name)
1183 {
1184 m_currentMenu = menu;
1185 break;
1186 }
1187 }
1188 }
1189
1190 if (!m_currentMenu) // Not found?
1191 {
1192 // Create menu
1193 m_currentMenu = new SubMenu;
1194 m_currentMenu->name = name;
1195
1196 if (parentMenu)
1197 parentMenu->subMenus.append(m_currentMenu);
1198 else
1199 m_rootMenu = m_currentMenu;
1200 }
1201 if (directoryFile.isEmpty())
1202 {
1203 kDebug(7021) << "Menu" << name << "does not specify a directory file.";
1204 }
1205
1206 // Override previous directoryFile iff available
1207 QString tmp = locateDirectoryFile(directoryFile);
1208 if (! tmp.isEmpty())
1209 m_currentMenu->directoryFile = tmp;
1210 m_currentMenu->isDeleted = isDeleted;
1211
1212 m_currentMenu->defaultLayoutNode = defaultLayoutNode;
1213 m_currentMenu->layoutNode = layoutNode;
1214 }
1215 else
1216 {
1217 // Look up menu
1218 if (parentMenu)
1219 {
1220 foreach (SubMenu *menu, parentMenu->subMenus)
1221 {
1222 if (menu->name == name)
1223 {
1224 m_currentMenu = menu;
1225 break;
1226 }
1227 }
1228 }
1229 else
1230 {
1231 m_currentMenu = m_rootMenu;
1232 }
1233 }
1234
1235 // Process AppDir and LegacyDir
1236 if (pass == 0)
1237 {
1238 QDomElement query;
1239 QDomNode n = docElem.firstChild();
1240 while( !n.isNull() ) {
1241 QDomElement e = n.toElement(); // try to convert the node to an element.
1242 if (e.tagName() == "AppDir")
1243 {
1244 createAppsInfo();
1245 QString dir = absoluteDir(e.text(), e.attribute("__BaseDir"));
1246
1247 registerDirectory(dir);
1248
1249 loadApplications(dir, QString());
1250 }
1251 else if (e.tagName() == "KDELegacyDirs")
1252 {
1253 createAppsInfo();
1254 if (!kdeLegacyDirsDone)
1255 {
1256kDebug(7021) << "Processing KDE Legacy dirs for <KDE>";
1257 SubMenu *oldMenu = m_currentMenu;
1258 m_currentMenu = new SubMenu;
1259
1260 processKDELegacyDirs();
1261
1262 m_legacyNodes.insert("<KDE>", m_currentMenu);
1263 m_currentMenu = oldMenu;
1264
1265 kdeLegacyDirsDone = true;
1266 }
1267 }
1268 else if (e.tagName() == "LegacyDir")
1269 {
1270 createAppsInfo();
1271 QString dir = absoluteDir(e.text(), e.attribute("__BaseDir"));
1272
1273 QString prefix = e.attributes().namedItem("prefix").toAttr().value();
1274
1275#ifndef Q_OS_WIN
1276 if (m_defaultLegacyDirs.contains(dir))
1277#else
1278 if (m_defaultLegacyDirs.contains(dir, Qt::CaseInsensitive))
1279#endif
1280 {
1281 if (!kdeLegacyDirsDone)
1282 {
1283kDebug(7021) << "Processing KDE Legacy dirs for" << dir;
1284 SubMenu *oldMenu = m_currentMenu;
1285 m_currentMenu = new SubMenu;
1286
1287 processKDELegacyDirs();
1288
1289 m_legacyNodes.insert("<KDE>", m_currentMenu);
1290 m_currentMenu = oldMenu;
1291
1292 kdeLegacyDirsDone = true;
1293 }
1294 }
1295 else
1296 {
1297 SubMenu *oldMenu = m_currentMenu;
1298 m_currentMenu = new SubMenu;
1299
1300 registerDirectory(dir);
1301
1302 processLegacyDir(dir, QString(), prefix);
1303
1304 m_legacyNodes.insert(dir, m_currentMenu);
1305 m_currentMenu = oldMenu;
1306 }
1307 }
1308 n = n.nextSibling();
1309 }
1310 }
1311
1312 loadAppsInfo(); // Update the scope wrt the list of applications
1313
1314 if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated))
1315 {
1316 n = docElem.firstChild();
1317
1318 while( !n.isNull() ) {
1319 QDomElement e = n.toElement(); // try to convert the node to an element.
1320 if (e.tagName() == "Include")
1321 {
1322 QHash<QString,KService::Ptr> items;
1323
1324 QDomNode n2 = e.firstChild();
1325 while( !n2.isNull() ) {
1326 QDomElement e2 = n2.toElement();
1327 items.clear();
1328 processCondition(e2, items);
1329 if (m_track)
1330 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, "Before <Include>");
1331 includeItems(m_currentMenu->items, items);
1332 excludeItems(m_currentMenu->excludeItems, items);
1333 markUsedApplications(items);
1334
1335 if (m_track)
1336 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, "After <Include>");
1337
1338 n2 = n2.nextSibling();
1339 }
1340 }
1341
1342 else if (e.tagName() == "Exclude")
1343 {
1344 QHash<QString,KService::Ptr> items;
1345
1346 QDomNode n2 = e.firstChild();
1347 while( !n2.isNull() ) {
1348 QDomElement e2 = n2.toElement();
1349 items.clear();
1350 processCondition(e2, items);
1351 if (m_track)
1352 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, "Before <Exclude>");
1353 excludeItems(m_currentMenu->items, items);
1354 includeItems(m_currentMenu->excludeItems, items);
1355 if (m_track)
1356 track(m_trackId, m_currentMenu->name, m_currentMenu->items, m_currentMenu->excludeItems, items, "After <Exclude>");
1357 n2 = n2.nextSibling();
1358 }
1359 }
1360
1361 n = n.nextSibling();
1362 }
1363 }
1364
1365 n = docElem.firstChild();
1366 while( !n.isNull() ) {
1367 QDomElement e = n.toElement(); // try to convert the node to an element.
1368 if (e.tagName() == "Menu")
1369 {
1370 processMenu(e, pass);
1371 }
1372// We insert legacy dir in pass 0, this way the order in the .menu-file determines
1373// which .directory file gets used, but the menu-entries of legacy-menus will always
1374// have the lowest priority.
1375// else if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated))
1376 else if (pass == 0)
1377 {
1378 if (e.tagName() == "LegacyDir")
1379 {
1380 // Add legacy nodes to Menu structure
1381 QString dir = absoluteDir(e.text(), e.attribute("__BaseDir"));
1382 SubMenu *legacyMenu = m_legacyNodes[dir];
1383 if (legacyMenu)
1384 {
1385 mergeMenu(m_currentMenu, legacyMenu);
1386 }
1387 }
1388
1389 else if (e.tagName() == "KDELegacyDirs")
1390 {
1391 // Add legacy nodes to Menu structure
1392 QString dir = "<KDE>";
1393 SubMenu *legacyMenu = m_legacyNodes[dir];
1394 if (legacyMenu)
1395 {
1396 mergeMenu(m_currentMenu, legacyMenu);
1397 }
1398 }
1399 }
1400 n = n.nextSibling();
1401 }
1402
1403 if (pass == 2)
1404 {
1405 n = docElem.firstChild();
1406 while( !n.isNull() ) {
1407 QDomElement e = n.toElement(); // try to convert the node to an element.
1408 if (e.tagName() == "Move")
1409 {
1410 QString orig;
1411 QString dest;
1412 QDomNode n2 = e.firstChild();
1413 while( !n2.isNull() ) {
1414 QDomElement e2 = n2.toElement(); // try to convert the node to an element.
1415 if( e2.tagName() == "Old")
1416 orig = e2.text();
1417 if( e2.tagName() == "New")
1418 dest = e2.text();
1419 n2 = n2.nextSibling();
1420 }
1421 kDebug(7021) << "Moving" << orig << "to" << dest;
1422 if (!orig.isEmpty() && !dest.isEmpty())
1423 {
1424 SubMenu *menu = takeSubMenu(m_currentMenu, orig);
1425 if (menu)
1426 {
1427 insertSubMenu(m_currentMenu, dest, menu, true); // Destination has priority
1428 }
1429 }
1430 }
1431 n = n.nextSibling();
1432 }
1433
1434 }
1435
1436 unloadAppsInfo(); // Update the scope wrt the list of applications
1437
1438 while (m_directoryDirs.count() > oldDirectoryDirsCount)
1439 m_directoryDirs.pop_front();
1440
1441 m_currentMenu = parentMenu;
1442}
1443
1444
1445
1446static QString parseAttribute( const QDomElement &e)
1447{
1448 QString option;
1449 if ( e.hasAttribute( "show_empty" ) )
1450 {
1451 QString str = e.attribute( "show_empty" );
1452 if ( str=="true" )
1453 option= "ME ";
1454 else if ( str=="false" )
1455 option= "NME ";
1456 else
1457 kDebug()<<" Error in parsing show_empty attribute :"<<str;
1458 }
1459 if ( e.hasAttribute( "inline" ) )
1460 {
1461 QString str = e.attribute( "inline" );
1462 if ( str=="true" )
1463 option+="I ";
1464 else if ( str=="false" )
1465 option+="NI ";
1466 else
1467 kDebug()<<" Error in parsing inline attribute :"<<str;
1468 }
1469 if ( e.hasAttribute( "inline_limit" ) )
1470 {
1471 bool ok;
1472 int value = e.attribute( "inline_limit" ).toInt(&ok);
1473 if ( ok )
1474 option+=QString( "IL[%1] " ).arg( value );
1475 }
1476 if ( e.hasAttribute( "inline_header" ) )
1477 {
1478 QString str = e.attribute( "inline_header" );
1479 if ( str=="true")
1480 option+="IH ";
1481 else if ( str == "false" )
1482 option+="NIH ";
1483 else
1484 kDebug()<<" Error in parsing of inline_header attribute :"<<str;
1485
1486 }
1487 if ( e.hasAttribute( "inline_alias" ) && e.attribute( "inline_alias" )=="true")
1488 {
1489 QString str = e.attribute( "inline_alias" );
1490 if ( str=="true" )
1491 option+="IA";
1492 else if ( str=="false" )
1493 option+="NIA";
1494 else
1495 kDebug()<<" Error in parsing inline_alias attribute :"<<str;
1496 }
1497 if( !option.isEmpty())
1498 {
1499 option = option.prepend(":O");
1500 }
1501 return option;
1502
1503}
1504
1505static QStringList parseLayoutNode(const QDomElement &docElem)
1506{
1507 QStringList layout;
1508
1509 QString optionDefaultLayout;
1510 if( docElem.tagName()=="DefaultLayout")
1511 optionDefaultLayout = parseAttribute( docElem);
1512 if ( !optionDefaultLayout.isEmpty() )
1513 layout.append( optionDefaultLayout );
1514
1515 bool mergeTagExists = false;
1516 QDomNode n = docElem.firstChild();
1517 while( !n.isNull() ) {
1518 QDomElement e = n.toElement(); // try to convert the node to an element.
1519 if (e.tagName() == "Separator")
1520 {
1521 layout.append(":S");
1522 }
1523 else if (e.tagName() == "Filename")
1524 {
1525 layout.append(e.text());
1526 }
1527 else if (e.tagName() == "Menuname")
1528 {
1529 layout.append('/'+e.text());
1530 QString option = parseAttribute( e );
1531 if( !option.isEmpty())
1532 layout.append( option );
1533 }
1534 else if (e.tagName() == "Merge")
1535 {
1536 QString type = e.attributeNode("type").value();
1537 if (type == "files")
1538 layout.append(":F");
1539 else if (type == "menus")
1540 layout.append(":M");
1541 else if (type == "all")
1542 layout.append(":A");
1543 mergeTagExists = true;
1544 }
1545
1546 n = n.nextSibling();
1547 }
1548
1549 if ( !mergeTagExists ) {
1550 layout.append(":M");
1551 layout.append(":F");
1552 kWarning() << "The menu spec file contains a Layout or DefaultLayout tag without the mandatory Merge tag inside. Please fix your file.";
1553 }
1554 return layout;
1555}
1556
1557void
1558VFolderMenu::layoutMenu(VFolderMenu::SubMenu *menu, QStringList defaultLayout) //krazy:exclude=passbyvalue
1559{
1560 if (!menu->defaultLayoutNode.isNull())
1561 {
1562 defaultLayout = parseLayoutNode(menu->defaultLayoutNode);
1563 }
1564
1565 if (menu->layoutNode.isNull())
1566 {
1567 menu->layoutList = defaultLayout;
1568 }
1569 else
1570 {
1571 menu->layoutList = parseLayoutNode(menu->layoutNode);
1572 if (menu->layoutList.isEmpty())
1573 menu->layoutList = defaultLayout;
1574 }
1575
1576 foreach (VFolderMenu::SubMenu *subMenu, menu->subMenus)
1577 {
1578 layoutMenu(subMenu, defaultLayout);
1579 }
1580}
1581
1582void
1583VFolderMenu::markUsedApplications(const QHash<QString,KService::Ptr>& items)
1584{
1585 foreach(const KService::Ptr &p, items)
1586 m_usedAppsDict.insert(p->menuId());
1587}
1588
1589VFolderMenu::SubMenu *
1590VFolderMenu::parseMenu(const QString &file, bool forceLegacyLoad)
1591{
1592 m_forcedLegacyLoad = false;
1593 m_legacyLoaded = false;
1594 m_appsInfo = 0;
1595
1596 const QStringList dirs = KGlobal::dirs()->resourceDirs("xdgconf-menu");
1597 for(QStringList::ConstIterator it=dirs.begin();
1598 it != dirs.end(); ++it)
1599 {
1600 registerDirectory(*it);
1601 }
1602
1603 loadMenu(file);
1604
1605 delete m_rootMenu;
1606 m_rootMenu = m_currentMenu = 0;
1607
1608 QDomElement docElem = m_doc.documentElement();
1609
1610 for (int pass = 0; pass <= 2; pass++)
1611 {
1612 // pass 0: load application desktop files
1613 // pass 1: the normal processing
1614 // pass 2: process <OnlyUnallocated> to put unused files into "Lost & Found".
1615 processMenu(docElem, pass);
1616
1617 switch (pass) {
1618 case 0:
1619 // Fill the dictCategories for each AppsInfo in m_appsInfoList,
1620 // in preparation for processMenu pass 1.
1621 buildApplicationIndex(false);
1622 break;
1623 case 1:
1624 // Fill the dictCategories for each AppsInfo in m_appsInfoList,
1625 // with only the unused apps, in preparation for processMenu pass 2.
1626 buildApplicationIndex(true /* unusedOnly */);
1627 break;
1628 case 2:
1629 {
1630 QStringList defaultLayout;
1631 defaultLayout << ":M"; // Sub-Menus
1632 defaultLayout << ":F"; // Individual entries
1633 layoutMenu(m_rootMenu, defaultLayout);
1634 break;
1635 }
1636 default:
1637 break;
1638 }
1639 }
1640
1641 if (!m_legacyLoaded && forceLegacyLoad)
1642 {
1643 m_forcedLegacyLoad = true;
1644 processKDELegacyDirs();
1645 }
1646
1647 return m_rootMenu;
1648}
1649
1650void
1651VFolderMenu::setTrackId(const QString &id)
1652{
1653 m_track = !id.isEmpty();
1654 m_trackId = id;
1655}
1656
1657#include "vfolder_menu.moc"
1658