diff options
Diffstat (limited to 'kded/vfolder_menu.cpp')
-rw-r--r-- | kded/vfolder_menu.cpp | 1681 |
1 files changed, 1681 insertions, 0 deletions
diff --git a/kded/vfolder_menu.cpp b/kded/vfolder_menu.cpp new file mode 100644 index 000000000..f73ef0c8e --- /dev/null +++ b/kded/vfolder_menu.cpp @@ -0,0 +1,1681 @@ +/* This file is part of the KDE libraries + * Copyright (C) 2003 Waldo Bastian <bastian@kde.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License version 2 as published by the Free Software Foundation; + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + **/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <dirent.h> +#include <stdlib.h> // getenv + +#include <kdebug.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <kservice.h> +#include <kde_file.h> + +#include <qmap.h> +#include <qfile.h> +#include <qdir.h> +#include <qregexp.h> + +#include "vfolder_menu.h" + +static void foldNode(QDomElement &docElem, QDomElement &e, QMap<QString,QDomElement> &dupeList, QString s=QString::null) +{ + if (s.isEmpty()) + s = e.text(); + QMap<QString,QDomElement>::iterator it = dupeList.find(s); + if (it != dupeList.end()) + { + kdDebug(7021) << e.tagName() << " and " << s << " requires combining!" << endl; + + docElem.removeChild(*it); + dupeList.remove(it); + } + dupeList.insert(s, e); +} + +static void replaceNode(QDomElement &docElem, QDomNode &n, const QStringList &list, const QString &tag) +{ + for(QStringList::ConstIterator it = list.begin(); + it != list.end(); ++it) + { + QDomElement e = docElem.ownerDocument().createElement(tag); + QDomText txt = docElem.ownerDocument().createTextNode(*it); + e.appendChild(txt); + docElem.insertAfter(e, n); + } + + QDomNode next = n.nextSibling(); + docElem.removeChild(n); + n = next; +// kdDebug(7021) << "Next tag = " << n.toElement().tagName() << endl; +} + +void VFolderMenu::registerFile(const QString &file) +{ + int i = file.findRev('/'); + if (i < 0) + return; + + QString dir = file.left(i+1); // Include trailing '/' + registerDirectory(dir); +} + +void VFolderMenu::registerDirectory(const QString &directory) +{ + m_allDirectories.append(directory); +} + +QStringList VFolderMenu::allDirectories() +{ + if (m_allDirectories.isEmpty()) + return m_allDirectories; + m_allDirectories.sort(); + + QStringList::Iterator it = m_allDirectories.begin(); + QString previous = *it++; + for(;it != m_allDirectories.end();) + { + if ((*it).startsWith(previous)) + { + it = m_allDirectories.remove(it); + } + else + { + previous = *it; + ++it; + } + } + return m_allDirectories; +} + +static void +track(const QString &menuId, const QString &menuName, QDict<KService> *includeList, QDict<KService> *excludeList, QDict<KService> *itemList, const QString &comment) +{ + if (itemList->find(menuId)) + printf("%s: %s INCL %d EXCL %d\n", menuName.latin1(), comment.latin1(), includeList->find(menuId) ? 1 : 0, excludeList->find(menuId) ? 1 : 0); +} + +void +VFolderMenu::includeItems(QDict<KService> *items1, QDict<KService> *items2) +{ + for(QDictIterator<KService> it(*items2); it.current(); ++it) + { + items1->replace(it.current()->menuId(), it.current()); + } +} + +void +VFolderMenu::matchItems(QDict<KService> *items1, QDict<KService> *items2) +{ + for(QDictIterator<KService> it(*items1); it.current(); ) + { + QString id = it.current()->menuId(); + ++it; + if (!items2->find(id)) + items1->remove(id); + } +} + +void +VFolderMenu::excludeItems(QDict<KService> *items1, QDict<KService> *items2) +{ + for(QDictIterator<KService> it(*items2); it.current(); ++it) + { + items1->remove(it.current()->menuId()); + } +} + +VFolderMenu::SubMenu* +VFolderMenu::takeSubMenu(SubMenu *parentMenu, const QString &menuName) +{ + int i = menuName.find('/'); + QString s1 = i > 0 ? menuName.left(i) : menuName; + QString s2 = menuName.mid(i+1); + + // Look up menu + for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next()) + { + if (menu->name == s1) + { + if (i == -1) + { + // Take it out + return parentMenu->subMenus.take(); + } + else + { + return takeSubMenu(menu, s2); + } + } + } + return 0; // Not found +} + +void +VFolderMenu::mergeMenu(SubMenu *menu1, SubMenu *menu2, bool reversePriority) +{ + if (m_track) + { + track(m_trackId, menu1->name, &(menu1->items), &(menu1->excludeItems), &(menu2->items), QString("Before MenuMerge w. %1 (incl)").arg(menu2->name)); + track(m_trackId, menu1->name, &(menu1->items), &(menu1->excludeItems), &(menu2->excludeItems), QString("Before MenuMerge w. %1 (excl)").arg(menu2->name)); + } + if (reversePriority) + { + // Merge menu1 with menu2, menu1 takes precedent + excludeItems(&(menu2->items), &(menu1->excludeItems)); + includeItems(&(menu1->items), &(menu2->items)); + excludeItems(&(menu2->excludeItems), &(menu1->items)); + includeItems(&(menu1->excludeItems), &(menu2->excludeItems)); + } + else + { + // Merge menu1 with menu2, menu2 takes precedent + excludeItems(&(menu1->items), &(menu2->excludeItems)); + includeItems(&(menu1->items), &(menu2->items)); + includeItems(&(menu1->excludeItems), &(menu2->excludeItems)); + menu1->isDeleted = menu2->isDeleted; + } + for(; menu2->subMenus.first(); ) + { + SubMenu *subMenu = menu2->subMenus.take(); + insertSubMenu(menu1, subMenu->name, subMenu, reversePriority); + } + + if (reversePriority) + { + // Merge menu1 with menu2, menu1 takes precedent + if (menu1->directoryFile.isEmpty()) + menu1->directoryFile = menu2->directoryFile; + if (menu1->defaultLayoutNode.isNull()) + menu1->defaultLayoutNode = menu2->defaultLayoutNode; + if (menu1->layoutNode.isNull()) + menu1->layoutNode = menu2->layoutNode; + } + else + { + // Merge menu1 with menu2, menu2 takes precedent + if (!menu2->directoryFile.isEmpty()) + menu1->directoryFile = menu2->directoryFile; + if (!menu2->defaultLayoutNode.isNull()) + menu1->defaultLayoutNode = menu2->defaultLayoutNode; + if (!menu2->layoutNode.isNull()) + menu1->layoutNode = menu2->layoutNode; + } + + if (m_track) + { + track(m_trackId, menu1->name, &(menu1->items), &(menu1->excludeItems), &(menu2->items), QString("After MenuMerge w. %1 (incl)").arg(menu2->name)); + track(m_trackId, menu1->name, &(menu1->items), &(menu1->excludeItems), &(menu2->excludeItems), QString("After MenuMerge w. %1 (excl)").arg(menu2->name)); + } + + delete menu2; +} + +void +VFolderMenu::insertSubMenu(SubMenu *parentMenu, const QString &menuName, SubMenu *newMenu, bool reversePriority) +{ + int i = menuName.find('/'); + + QString s1 = menuName.left(i); + QString s2 = menuName.mid(i+1); + + // Look up menu + for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next()) + { + if (menu->name == s1) + { + if (i == -1) + { + mergeMenu(menu, newMenu, reversePriority); + return; + } + else + { + insertSubMenu(menu, s2, newMenu, reversePriority); + return; + } + } + } + if (i == -1) + { + // Add it here + newMenu->name = menuName; + parentMenu->subMenus.append(newMenu); + } + else + { + SubMenu *menu = new SubMenu; + menu->name = s1; + parentMenu->subMenus.append(menu); + insertSubMenu(menu, s2, newMenu); + } +} + +void +VFolderMenu::insertService(SubMenu *parentMenu, const QString &name, KService *newService) +{ + int i = name.find('/'); + + if (i == -1) + { + // Add it here + parentMenu->items.replace(newService->menuId(), newService); + return; + } + + QString s1 = name.left(i); + QString s2 = name.mid(i+1); + + // Look up menu + for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next()) + { + if (menu->name == s1) + { + insertService(menu, s2, newService); + return; + } + } + + SubMenu *menu = new SubMenu; + menu->name = s1; + parentMenu->subMenus.append(menu); + insertService(menu, s2, newService); +} + + +VFolderMenu::VFolderMenu() : m_usedAppsDict(797), m_track(false) +{ + m_rootMenu = 0; + initDirs(); +} + +VFolderMenu::~VFolderMenu() +{ + delete m_rootMenu; +} + +#define FOR_ALL_APPLICATIONS(it) \ + for(appsInfo *info = m_appsInfoStack.first(); \ + info; info = m_appsInfoStack.next()) \ + { \ + for(QDictIterator<KService> it( info->applications ); \ + it.current(); ++it ) \ + { +#define FOR_ALL_APPLICATIONS_END } } + +#define FOR_CATEGORY(category, it) \ + for(appsInfo *info = m_appsInfoStack.first(); \ + info; info = m_appsInfoStack.next()) \ + { \ + KService::List *list = info->dictCategories.find(category); \ + if (list) for(KService::List::ConstIterator it = list->begin(); \ + it != list->end(); ++it) \ + { +#define FOR_CATEGORY_END } } + +KService * +VFolderMenu::findApplication(const QString &relPath) +{ + for(appsInfo *info = m_appsInfoStack.first(); + info; info = m_appsInfoStack.next()) + { + KService *s = info->applications.find(relPath); + if (s) + return s; + } + return 0; +} + +void +VFolderMenu::addApplication(const QString &id, KService *service) +{ + service->setMenuId(id); + m_appsInfo->applications.replace(id, service); +} + +void +VFolderMenu::buildApplicationIndex(bool unusedOnly) +{ + QPtrList<appsInfo>::ConstIterator appsInfo_it = m_appsInfoList.begin(); + for( ; appsInfo_it != m_appsInfoList.end(); ++appsInfo_it ) + { + appsInfo *info = *appsInfo_it; + info->dictCategories.clear(); + for(QDictIterator<KService> it( info->applications ); + it.current(); ) + { + KService *s = it.current(); + QDictIterator<KService> tmpIt = it; + ++it; + if (unusedOnly && m_usedAppsDict.find(s->menuId())) + { + // Remove and skip this one + info->applications.remove(tmpIt.currentKey()); + continue; + } + + QStringList cats = s->categories(); + for(QStringList::ConstIterator it2 = cats.begin(); + it2 != cats.end(); ++it2) + { + const QString &cat = *it2; + KService::List *list = info->dictCategories.find(cat); + if (!list) + { + list = new KService::List(); + info->dictCategories.insert(cat, list); + } + list->append(s); + } + } + } +} + +void +VFolderMenu::createAppsInfo() +{ + if (m_appsInfo) return; + + m_appsInfo = new appsInfo; + m_appsInfoStack.prepend(m_appsInfo); + m_appsInfoList.append(m_appsInfo); + m_currentMenu->apps_info = m_appsInfo; +} + +void +VFolderMenu::loadAppsInfo() +{ + m_appsInfo = m_currentMenu->apps_info; + if (!m_appsInfo) + return; // No appsInfo for this menu + + if (m_appsInfoStack.first() == m_appsInfo) + return; // Already added (By createAppsInfo?) + + m_appsInfoStack.prepend(m_appsInfo); // Add +} + +void +VFolderMenu::unloadAppsInfo() +{ + m_appsInfo = m_currentMenu->apps_info; + if (!m_appsInfo) + return; // No appsInfo for this menu + + if (m_appsInfoStack.first() != m_appsInfo) + { + return; // Already removed (huh?) + } + + m_appsInfoStack.remove(m_appsInfo); // Remove + m_appsInfo = 0; +} + +QString +VFolderMenu::absoluteDir(const QString &_dir, const QString &baseDir, bool keepRelativeToCfg) +{ + QString dir = _dir; + if (QDir::isRelativePath(dir)) + { + dir = baseDir + dir; + } + if (!dir.endsWith("/")) + dir += '/'; + + if (QDir::isRelativePath(dir) && !keepRelativeToCfg) + { + dir = KGlobal::dirs()->findResource("xdgconf-menu", dir); + } + + dir = KGlobal::dirs()->realPath(dir); + + return dir; +} + +static void tagBaseDir(QDomDocument &doc, const QString &tag, const QString &dir) +{ + QDomNodeList mergeFileList = doc.elementsByTagName(tag); + for(int i = 0; i < (int)mergeFileList.count(); i++) + { + QDomAttr attr = doc.createAttribute("__BaseDir"); + attr.setValue(dir); + mergeFileList.item(i).toElement().setAttributeNode(attr); + } +} + +static void tagBasePath(QDomDocument &doc, const QString &tag, const QString &path) +{ + QDomNodeList mergeFileList = doc.elementsByTagName(tag); + for(int i = 0; i < (int)mergeFileList.count(); i++) + { + QDomAttr attr = doc.createAttribute("__BasePath"); + attr.setValue(path); + mergeFileList.item(i).toElement().setAttributeNode(attr); + } +} + +QDomDocument +VFolderMenu::loadDoc() +{ + QDomDocument doc; + if ( m_docInfo.path.isEmpty() ) + { + return doc; + } + QFile file( m_docInfo.path ); + if ( !file.open( IO_ReadOnly ) ) + { + kdWarning(7021) << "Could not open " << m_docInfo.path << endl; + return doc; + } + QString errorMsg; + int errorRow; + int errorCol; + if ( !doc.setContent( &file, &errorMsg, &errorRow, &errorCol ) ) { + kdWarning(7021) << "Parse error in " << m_docInfo.path << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg << endl; + file.close(); + return doc; + } + file.close(); + + tagBaseDir(doc, "MergeFile", m_docInfo.baseDir); + tagBasePath(doc, "MergeFile", m_docInfo.path); + tagBaseDir(doc, "MergeDir", m_docInfo.baseDir); + tagBaseDir(doc, "DirectoryDir", m_docInfo.baseDir); + tagBaseDir(doc, "AppDir", m_docInfo.baseDir); + tagBaseDir(doc, "LegacyDir", m_docInfo.baseDir); + + return doc; +} + + +void +VFolderMenu::mergeFile(QDomElement &parent, const QDomNode &mergeHere) +{ +kdDebug(7021) << "VFolderMenu::mergeFile: " << m_docInfo.path << endl; + QDomDocument doc = loadDoc(); + + QDomElement docElem = doc.documentElement(); + QDomNode n = docElem.firstChild(); + QDomNode last = mergeHere; + while( !n.isNull() ) + { + QDomElement e = n.toElement(); // try to convert the node to an element. + QDomNode next = n.nextSibling(); + + if (e.isNull()) + { + // Skip + } + // The spec says we must ignore any Name nodes + else if (e.tagName() != "Name") + { + parent.insertAfter(n, last); + last = n; + } + + docElem.removeChild(n); + n = next; + } +} + + +void +VFolderMenu::mergeMenus(QDomElement &docElem, QString &name) +{ + QMap<QString,QDomElement> menuNodes; + QMap<QString,QDomElement> directoryNodes; + QMap<QString,QDomElement> appDirNodes; + QMap<QString,QDomElement> directoryDirNodes; + QMap<QString,QDomElement> legacyDirNodes; + QDomElement defaultLayoutNode; + QDomElement layoutNode; + + QDomNode n = docElem.firstChild(); + while( !n.isNull() ) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if( e.isNull() ) { +// kdDebug(7021) << "Empty node" << endl; + } + else if( e.tagName() == "DefaultAppDirs") { + // Replace with m_defaultAppDirs + replaceNode(docElem, n, m_defaultAppDirs, "AppDir"); + continue; + } + else if( e.tagName() == "DefaultDirectoryDirs") { + // Replace with m_defaultDirectoryDirs + replaceNode(docElem, n, m_defaultDirectoryDirs, "DirectoryDir"); + continue; + } + else if( e.tagName() == "DefaultMergeDirs") { + // Replace with m_defaultMergeDirs + replaceNode(docElem, n, m_defaultMergeDirs, "MergeDir"); + continue; + } + else if( e.tagName() == "AppDir") { + // Filter out dupes + foldNode(docElem, e, appDirNodes); + } + else if( e.tagName() == "DirectoryDir") { + // Filter out dupes + foldNode(docElem, e, directoryDirNodes); + } + else if( e.tagName() == "LegacyDir") { + // Filter out dupes + foldNode(docElem, e, legacyDirNodes); + } + else if( e.tagName() == "Directory") { + // Filter out dupes + foldNode(docElem, e, directoryNodes); + } + else if( e.tagName() == "Move") { + // Filter out dupes + QString orig; + QDomNode n2 = e.firstChild(); + while( !n2.isNull() ) { + QDomElement e2 = n2.toElement(); // try to convert the node to an element. + if( e2.tagName() == "Old") + { + orig = e2.text(); + break; + } + n2 = n2.nextSibling(); + } + foldNode(docElem, e, appDirNodes, orig); + } + else if( e.tagName() == "Menu") { + QString name; + mergeMenus(e, name); + QMap<QString,QDomElement>::iterator it = menuNodes.find(name); + if (it != menuNodes.end()) + { + QDomElement docElem2 = *it; + QDomNode n2 = docElem2.firstChild(); + QDomNode first = e.firstChild(); + while( !n2.isNull() ) { + QDomElement e2 = n2.toElement(); // try to convert the node to an element. + QDomNode n3 = n2.nextSibling(); + e.insertBefore(n2, first); + docElem2.removeChild(n2); + n2 = n3; + } + // We still have duplicated Name entries + // but we don't care about that + + docElem.removeChild(docElem2); + menuNodes.remove(it); + } + menuNodes.insert(name, e); + } + else if( e.tagName() == "MergeFile") { + if ((e.attribute("type") == "parent")) + pushDocInfoParent(e.attribute("__BasePath"), e.attribute("__BaseDir")); + else + pushDocInfo(e.text(), e.attribute("__BaseDir")); + + if (!m_docInfo.path.isEmpty()) + mergeFile(docElem, n); + popDocInfo(); + + QDomNode last = n; + n = n.nextSibling(); + docElem.removeChild(last); // Remove the MergeFile node + continue; + } + else if( e.tagName() == "MergeDir") { + QString dir = absoluteDir(e.text(), e.attribute("__BaseDir"), true); + + QStringList dirs = KGlobal::dirs()->findDirs("xdgconf-menu", dir); + for(QStringList::ConstIterator it=dirs.begin(); + it != dirs.end(); ++it) + { + registerDirectory(*it); + } + + QStringList fileList; + if (!QDir::isRelativePath(dir)) + { + // Absolute + fileList = KGlobal::dirs()->findAllResources("xdgconf-menu", dir+"*.menu", false, false); + } + else + { + // Relative + (void) KGlobal::dirs()->findAllResources("xdgconf-menu", dir+"*.menu", false, true, fileList); + } + + for(QStringList::ConstIterator it=fileList.begin(); + it != fileList.end(); ++it) + { + pushDocInfo(*it); + mergeFile(docElem, n); + popDocInfo(); + } + + QDomNode last = n; + n = n.nextSibling(); + docElem.removeChild(last); // Remove the MergeDir node + + continue; + } + else if( e.tagName() == "Name") { + name = e.text(); + } + else if( e.tagName() == "DefaultLayout") { + if (!defaultLayoutNode.isNull()) + docElem.removeChild(defaultLayoutNode); + defaultLayoutNode = e; + } + else if( e.tagName() == "Layout") { + if (!layoutNode.isNull()) + docElem.removeChild(layoutNode); + layoutNode = e; + } + n = n.nextSibling(); + } +} + +void +VFolderMenu::pushDocInfo(const QString &fileName, const QString &baseDir) +{ + m_docInfoStack.push(m_docInfo); + if (!baseDir.isEmpty()) + { + if (!QDir::isRelativePath(baseDir)) + m_docInfo.baseDir = KGlobal::dirs()->relativeLocation("xdgconf-menu", baseDir); + else + m_docInfo.baseDir = baseDir; + } + + QString baseName = fileName; + if (!QDir::isRelativePath(baseName)) + registerFile(baseName); + else + baseName = m_docInfo.baseDir + baseName; + + m_docInfo.path = locateMenuFile(fileName); + if (m_docInfo.path.isEmpty()) + { + m_docInfo.baseDir = QString::null; + m_docInfo.baseName = QString::null; + kdDebug(7021) << "Menu " << fileName << " not found." << endl; + return; + } + int i; + i = baseName.findRev('/'); + if (i > 0) + { + m_docInfo.baseDir = baseName.left(i+1); + m_docInfo.baseName = baseName.mid(i+1, baseName.length() - i - 6); + } + else + { + m_docInfo.baseDir = QString::null; + m_docInfo.baseName = baseName.left( baseName.length() - 5 ); + } +} + +void +VFolderMenu::pushDocInfoParent(const QString &basePath, const QString &baseDir) +{ + m_docInfoStack.push(m_docInfo); + + m_docInfo.baseDir = baseDir; + + QString fileName = basePath.mid(basePath.findRev('/')+1); + m_docInfo.baseName = fileName.left( fileName.length() - 5 ); + QString baseName = QDir::cleanDirPath(m_docInfo.baseDir + fileName); + + QStringList result = KGlobal::dirs()->findAllResources("xdgconf-menu", baseName); + + while( !result.isEmpty() && (result[0] != basePath)) + result.remove(result.begin()); + + if (result.count() <= 1) + { + m_docInfo.path = QString::null; // No parent found + return; + } + m_docInfo.path = result[1]; +} + +void +VFolderMenu::popDocInfo() +{ + m_docInfo = m_docInfoStack.pop(); +} + +QString +VFolderMenu::locateMenuFile(const QString &fileName) +{ + if (!QDir::isRelativePath(fileName)) + { + if (KStandardDirs::exists(fileName)) + return fileName; + return QString::null; + } + + QString result; + + QString xdgMenuPrefix = QString::fromLocal8Bit(getenv("XDG_MENU_PREFIX")); + if (!xdgMenuPrefix.isEmpty()) + { + QFileInfo fileInfo(fileName); + + QString fileNameOnly = fileInfo.fileName(); + if (!fileNameOnly.startsWith(xdgMenuPrefix)) + fileNameOnly = xdgMenuPrefix + fileNameOnly; + + QString baseName = QDir::cleanDirPath(m_docInfo.baseDir + + fileInfo.dirPath() + "/" + + fileNameOnly); + result = locate("xdgconf-menu", baseName); + } + + if (result.isEmpty()) + { + QString baseName = QDir::cleanDirPath(m_docInfo.baseDir + fileName); + result = locate("xdgconf-menu", baseName); + } + + return result; +} + +QString +VFolderMenu::locateDirectoryFile(const QString &fileName) +{ + if (fileName.isEmpty()) + return QString::null; + + if (!QDir::isRelativePath(fileName)) + { + if (KStandardDirs::exists(fileName)) + return fileName; + return QString::null; + } + + // First location in the list wins + QString tmp; + for(QStringList::ConstIterator it = m_directoryDirs.begin(); + it != m_directoryDirs.end(); + ++it) + { + tmp = (*it)+fileName; + if (KStandardDirs::exists(tmp)) + return tmp; + } + + return QString::null; +} + +void +VFolderMenu::initDirs() +{ + m_defaultDataDirs = QStringList::split(':', KGlobal::dirs()->kfsstnd_prefixes()); + QString localDir = m_defaultDataDirs.first(); + m_defaultDataDirs.remove(localDir); // Remove local dir + + m_defaultAppDirs = KGlobal::dirs()->findDirs("xdgdata-apps", QString::null); + m_defaultDirectoryDirs = KGlobal::dirs()->findDirs("xdgdata-dirs", QString::null); + m_defaultLegacyDirs = KGlobal::dirs()->resourceDirs("apps"); +} + +void +VFolderMenu::loadMenu(const QString &fileName) +{ + m_defaultMergeDirs.clear(); + + if (!fileName.endsWith(".menu")) + return; + + pushDocInfo(fileName); + m_defaultMergeDirs << m_docInfo.baseName+"-merged/"; + m_doc = loadDoc(); + popDocInfo(); + + if (m_doc.isNull()) + { + if (m_docInfo.path.isEmpty()) + kdError(7021) << fileName << " not found in " << m_allDirectories << endl; + else + kdWarning(7021) << "Load error (" << m_docInfo.path << ")" << endl; + return; + } + + QDomElement e = m_doc.documentElement(); + QString name; + mergeMenus(e, name); +} + +void +VFolderMenu::processCondition(QDomElement &domElem, QDict<KService> *items) +{ + if (domElem.tagName() == "And") + { + QDomNode n = domElem.firstChild(); + // Look for the first child element + while (!n.isNull()) // loop in case of comments + { + QDomElement e = n.toElement(); + n = n.nextSibling(); + if ( !e.isNull() ) { + processCondition(e, items); + break; // we only want the first one + } + } + + QDict<KService> andItems; + while( !n.isNull() ) { + QDomElement e = n.toElement(); + if (e.tagName() == "Not") + { + // Special handling for "and not" + QDomNode n2 = e.firstChild(); + while( !n2.isNull() ) { + QDomElement e2 = n2.toElement(); + andItems.clear(); + processCondition(e2, &andItems); + excludeItems(items, &andItems); + n2 = n2.nextSibling(); + } + } + else + { + andItems.clear(); + processCondition(e, &andItems); + matchItems(items, &andItems); + } + n = n.nextSibling(); + } + } + else if (domElem.tagName() == "Or") + { + QDomNode n = domElem.firstChild(); + // Look for the first child element + while (!n.isNull()) // loop in case of comments + { + QDomElement e = n.toElement(); + n = n.nextSibling(); + if ( !e.isNull() ) { + processCondition(e, items); + break; // we only want the first one + } + } + + QDict<KService> orItems; + while( !n.isNull() ) { + QDomElement e = n.toElement(); + if ( !e.isNull() ) { + orItems.clear(); + processCondition(e, &orItems); + includeItems(items, &orItems); + } + n = n.nextSibling(); + } + } + else if (domElem.tagName() == "Not") + { + FOR_ALL_APPLICATIONS(it) + { + KService *s = it.current(); + items->replace(s->menuId(), s); + } + FOR_ALL_APPLICATIONS_END + + QDict<KService> notItems; + QDomNode n = domElem.firstChild(); + while( !n.isNull() ) { + QDomElement e = n.toElement(); + if ( !e.isNull() ) { + notItems.clear(); + processCondition(e, ¬Items); + excludeItems(items, ¬Items); + } + n = n.nextSibling(); + } + } + else if (domElem.tagName() == "Category") + { + FOR_CATEGORY(domElem.text(), it) + { + KService *s = *it; + items->replace(s->menuId(), s); + } + FOR_CATEGORY_END + } + else if (domElem.tagName() == "All") + { + FOR_ALL_APPLICATIONS(it) + { + KService *s = it.current(); + items->replace(s->menuId(), s); + } + FOR_ALL_APPLICATIONS_END + } + else if (domElem.tagName() == "Filename") + { + QString filename = domElem.text(); +kdDebug(7021) << "Adding file " << filename << endl; + KService *s = findApplication(filename); + if (s) + items->replace(filename, s); + } +} + +void +VFolderMenu::loadApplications(const QString &dir, const QString &prefix) +{ + kdDebug(7021) << "Looking up applications under " << dir << endl; + + // We look for a set of files. + DIR *dp = opendir( QFile::encodeName(dir)); + if (!dp) + return; + + struct dirent *ep; + KDE_struct_stat buff; + + QString _dot("."); + QString _dotdot(".."); + + while( ( ep = readdir( dp ) ) != 0L ) + { + QString fn( QFile::decodeName(ep->d_name)); + if (fn == _dot || fn == _dotdot || fn.at(fn.length() - 1).latin1() == '~') + continue; + + QString pathfn = dir + fn; + if ( KDE_stat( QFile::encodeName(pathfn), &buff ) != 0 ) { + continue; // Couldn't stat (e.g. no read permissions) + } + if ( S_ISDIR( buff.st_mode )) { + loadApplications(pathfn + '/', prefix + fn + '-'); + continue; + } + + if ( S_ISREG( buff.st_mode)) + { + if (!fn.endsWith(".desktop")) + continue; + + KService *service = 0; + emit newService(pathfn, &service); + if (service) + addApplication(prefix+fn, service); + } + } + closedir( dp ); +} + +void +VFolderMenu::processKDELegacyDirs() +{ +kdDebug(7021) << "processKDELegacyDirs()" << endl; + + QDict<KService> items; + QString prefix = "kde-"; + + QStringList relFiles; + QRegExp files("\\.(desktop|kdelnk)$"); + QRegExp dirs("\\.directory$"); + + (void) KGlobal::dirs()->findAllResources( "apps", + QString::null, + true, // Recursive! + true, // uniq + relFiles); + for(QStringList::ConstIterator it = relFiles.begin(); + it != relFiles.end(); ++it) + { + if (!m_forcedLegacyLoad && (dirs.search(*it) != -1)) + { + QString name = *it; + if (!name.endsWith("/.directory")) + continue; // Probably ".directory", skip it. + + name = name.left(name.length()-11); + + SubMenu *newMenu = new SubMenu; + newMenu->directoryFile = locate("apps", *it); + + insertSubMenu(m_currentMenu, name, newMenu); + continue; + } + + if (files.search(*it) != -1) + { + QString name = *it; + KService *service = 0; + emit newService(name, &service); + + if (service && !m_forcedLegacyLoad) + { + QString id = name; + // Strip path from id + int i = id.findRev('/'); + if (i >= 0) + id = id.mid(i+1); + + id.prepend(prefix); + + // TODO: add Legacy category + addApplication(id, service); + items.replace(service->menuId(), service); + if (service->categories().isEmpty()) + insertService(m_currentMenu, name, service); + + } + } + } + markUsedApplications(&items); + m_legacyLoaded = true; +} + +void +VFolderMenu::processLegacyDir(const QString &dir, const QString &relDir, const QString &prefix) +{ +kdDebug(7021) << "processLegacyDir(" << dir << ", " << relDir << ", " << prefix << ")" << endl; + + QDict<KService> items; + // We look for a set of files. + DIR *dp = opendir( QFile::encodeName(dir)); + if (!dp) + return; + + struct dirent *ep; + KDE_struct_stat buff; + + QString _dot("."); + QString _dotdot(".."); + + while( ( ep = readdir( dp ) ) != 0L ) + { + QString fn( QFile::decodeName(ep->d_name)); + if (fn == _dot || fn == _dotdot || fn.at(fn.length() - 1).latin1() == '~') + continue; + + QString pathfn = dir + fn; + if ( KDE_stat( QFile::encodeName(pathfn), &buff ) != 0 ) { + continue; // Couldn't stat (e.g. no read permissions) + } + if ( S_ISDIR( buff.st_mode )) { + SubMenu *parentMenu = m_currentMenu; + + m_currentMenu = new SubMenu; + m_currentMenu->name = fn; + m_currentMenu->directoryFile = dir + fn + "/.directory"; + + parentMenu->subMenus.append(m_currentMenu); + + processLegacyDir(pathfn + '/', relDir+fn+'/', prefix); + m_currentMenu = parentMenu; + continue; + } + + if ( S_ISREG( buff.st_mode)) + { + if (!fn.endsWith(".desktop")) + continue; + + KService *service = 0; + emit newService(pathfn, &service); + if (service) + { + QString id = prefix+fn; + + // TODO: Add legacy category + addApplication(id, service); + items.replace(service->menuId(), service); + + if (service->categories().isEmpty()) + m_currentMenu->items.replace(id, service); + } + } + } + closedir( dp ); + markUsedApplications(&items); +} + + + +void +VFolderMenu::processMenu(QDomElement &docElem, int pass) +{ + SubMenu *parentMenu = m_currentMenu; + unsigned int oldDirectoryDirsCount = m_directoryDirs.count(); + + QString name; + QString directoryFile; + bool onlyUnallocated = false; + bool isDeleted = false; + bool kdeLegacyDirsDone = false; + QDomElement defaultLayoutNode; + QDomElement layoutNode; + + QDomElement query; + QDomNode n = docElem.firstChild(); + while( !n.isNull() ) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (e.tagName() == "Name") + { + name = e.text(); + } + else if (e.tagName() == "Directory") + { + directoryFile = e.text(); + } + else if (e.tagName() == "DirectoryDir") + { + QString dir = absoluteDir(e.text(), e.attribute("__BaseDir")); + + m_directoryDirs.prepend(dir); + } + else if (e.tagName() == "OnlyUnallocated") + { + onlyUnallocated = true; + } + else if (e.tagName() == "NotOnlyUnallocated") + { + onlyUnallocated = false; + } + else if (e.tagName() == "Deleted") + { + isDeleted = true; + } + else if (e.tagName() == "NotDeleted") + { + isDeleted = false; + } + else if (e.tagName() == "DefaultLayout") + { + defaultLayoutNode = e; + } + else if (e.tagName() == "Layout") + { + layoutNode = e; + } + n = n.nextSibling(); + } + + // Setup current menu entry + if (pass == 0) + { + m_currentMenu = 0; + // Look up menu + if (parentMenu) + { + for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next()) + { + if (menu->name == name) + { + m_currentMenu = menu; + break; + } + } + } + + if (!m_currentMenu) // Not found? + { + // Create menu + m_currentMenu = new SubMenu; + m_currentMenu->name = name; + + if (parentMenu) + parentMenu->subMenus.append(m_currentMenu); + else + m_rootMenu = m_currentMenu; + } + if (directoryFile.isEmpty()) + { + kdDebug(7021) << "Menu " << name << " does not specify a directory file." << endl; + } + + // Override previous directoryFile iff available + QString tmp = locateDirectoryFile(directoryFile); + if (! tmp.isEmpty()) + m_currentMenu->directoryFile = tmp; + m_currentMenu->isDeleted = isDeleted; + + m_currentMenu->defaultLayoutNode = defaultLayoutNode; + m_currentMenu->layoutNode = layoutNode; + } + else + { + // Look up menu + if (parentMenu) + { + for(SubMenu *menu = parentMenu->subMenus.first(); menu; menu = parentMenu->subMenus.next()) + { + if (menu->name == name) + { + m_currentMenu = menu; + break; + } + } + } + else + { + m_currentMenu = m_rootMenu; + } + } + + // Process AppDir and LegacyDir + if (pass == 0) + { + QDomElement query; + QDomNode n = docElem.firstChild(); + while( !n.isNull() ) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (e.tagName() == "AppDir") + { + createAppsInfo(); + QString dir = absoluteDir(e.text(), e.attribute("__BaseDir")); + + registerDirectory(dir); + + loadApplications(dir, QString::null); + } + else if (e.tagName() == "KDELegacyDirs") + { + createAppsInfo(); + if (!kdeLegacyDirsDone) + { +kdDebug(7021) << "Processing KDE Legacy dirs for <KDE>" << endl; + SubMenu *oldMenu = m_currentMenu; + m_currentMenu = new SubMenu; + + processKDELegacyDirs(); + + m_legacyNodes.replace("<KDE>", m_currentMenu); + m_currentMenu = oldMenu; + + kdeLegacyDirsDone = true; + } + } + else if (e.tagName() == "LegacyDir") + { + createAppsInfo(); + QString dir = absoluteDir(e.text(), e.attribute("__BaseDir")); + + QString prefix = e.attributes().namedItem("prefix").toAttr().value(); + + if (m_defaultLegacyDirs.contains(dir)) + { + if (!kdeLegacyDirsDone) + { +kdDebug(7021) << "Processing KDE Legacy dirs for " << dir << endl; + SubMenu *oldMenu = m_currentMenu; + m_currentMenu = new SubMenu; + + processKDELegacyDirs(); + + m_legacyNodes.replace("<KDE>", m_currentMenu); + m_currentMenu = oldMenu; + + kdeLegacyDirsDone = true; + } + } + else + { + SubMenu *oldMenu = m_currentMenu; + m_currentMenu = new SubMenu; + + registerDirectory(dir); + + processLegacyDir(dir, QString::null, prefix); + + m_legacyNodes.replace(dir, m_currentMenu); + m_currentMenu = oldMenu; + } + } + n = n.nextSibling(); + } + } + + loadAppsInfo(); // Update the scope wrt the list of applications + + if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated)) + { + n = docElem.firstChild(); + + while( !n.isNull() ) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (e.tagName() == "Include") + { + QDict<KService> items; + + QDomNode n2 = e.firstChild(); + while( !n2.isNull() ) { + QDomElement e2 = n2.toElement(); + items.clear(); + processCondition(e2, &items); + if (m_track) + track(m_trackId, m_currentMenu->name, &(m_currentMenu->items), &(m_currentMenu->excludeItems), &items, "Before <Include>"); + includeItems(&(m_currentMenu->items), &items); + excludeItems(&(m_currentMenu->excludeItems), &items); + markUsedApplications(&items); + + if (m_track) + track(m_trackId, m_currentMenu->name, &(m_currentMenu->items), &(m_currentMenu->excludeItems), &items, "After <Include>"); + + n2 = n2.nextSibling(); + } + } + + else if (e.tagName() == "Exclude") + { + QDict<KService> items; + + QDomNode n2 = e.firstChild(); + while( !n2.isNull() ) { + QDomElement e2 = n2.toElement(); + items.clear(); + processCondition(e2, &items); + if (m_track) + track(m_trackId, m_currentMenu->name, &(m_currentMenu->items), &(m_currentMenu->excludeItems), &items, "Before <Exclude>"); + excludeItems(&(m_currentMenu->items), &items); + includeItems(&(m_currentMenu->excludeItems), &items); + if (m_track) + track(m_trackId, m_currentMenu->name, &(m_currentMenu->items), &(m_currentMenu->excludeItems), &items, "After <Exclude>"); + n2 = n2.nextSibling(); + } + } + + n = n.nextSibling(); + } + } + + n = docElem.firstChild(); + while( !n.isNull() ) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (e.tagName() == "Menu") + { + processMenu(e, pass); + } +// We insert legacy dir in pass 0, this way the order in the .menu-file determines +// which .directory file gets used, but the menu-entries of legacy-menus will always +// have the lowest priority. +// else if (((pass == 1) && !onlyUnallocated) || ((pass == 2) && onlyUnallocated)) + else if (pass == 0) + { + if (e.tagName() == "LegacyDir") + { + // Add legacy nodes to Menu structure + QString dir = absoluteDir(e.text(), e.attribute("__BaseDir")); + SubMenu *legacyMenu = m_legacyNodes.find(dir); + if (legacyMenu) + { + mergeMenu(m_currentMenu, legacyMenu); + } + } + + else if (e.tagName() == "KDELegacyDirs") + { + // Add legacy nodes to Menu structure + QString dir = "<KDE>"; + SubMenu *legacyMenu = m_legacyNodes.find(dir); + if (legacyMenu) + { + mergeMenu(m_currentMenu, legacyMenu); + } + } + } + n = n.nextSibling(); + } + + if (pass == 2) + { + n = docElem.firstChild(); + while( !n.isNull() ) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (e.tagName() == "Move") + { + QString orig; + QString dest; + QDomNode n2 = e.firstChild(); + while( !n2.isNull() ) { + QDomElement e2 = n2.toElement(); // try to convert the node to an element. + if( e2.tagName() == "Old") + orig = e2.text(); + if( e2.tagName() == "New") + dest = e2.text(); + n2 = n2.nextSibling(); + } + kdDebug(7021) << "Moving " << orig << " to " << dest << endl; + if (!orig.isEmpty() && !dest.isEmpty()) + { + SubMenu *menu = takeSubMenu(m_currentMenu, orig); + if (menu) + { + insertSubMenu(m_currentMenu, dest, menu, true); // Destination has priority + } + } + } + n = n.nextSibling(); + } + + } + + unloadAppsInfo(); // Update the scope wrt the list of applications + + while (m_directoryDirs.count() > oldDirectoryDirsCount) + m_directoryDirs.pop_front(); + + m_currentMenu = parentMenu; +} + + + +static QString parseAttribute( const QDomElement &e) +{ + QString option; + if ( e.hasAttribute( "show_empty" ) ) + { + QString str = e.attribute( "show_empty" ); + if ( str=="true" ) + option= "ME "; + else if ( str=="false" ) + option= "NME "; + else + kdDebug()<<" Error in parsing show_empty attribute :"<<str<<endl; + } + if ( e.hasAttribute( "inline" ) ) + { + QString str = e.attribute( "inline" ); + if ( str=="true" ) + option+="I "; + else if ( str=="false" ) + option+="NI "; + else + kdDebug()<<" Error in parsing inlibe attribute :"<<str<<endl; + } + if ( e.hasAttribute( "inline_limit" ) ) + { + bool ok; + int value = e.attribute( "inline_limit" ).toInt(&ok); + if ( ok ) + option+=QString( "IL[%1] " ).arg( value ); + } + if ( e.hasAttribute( "inline_header" ) ) + { + QString str = e.attribute( "inline_header" ); + if ( str=="true") + option+="IH "; + else if ( str == "false" ) + option+="NIH "; + else + kdDebug()<<" Error in parsing of inline_header attribute :"<<str<<endl; + + } + if ( e.hasAttribute( "inline_alias" ) && e.attribute( "inline_alias" )=="true") + { + QString str = e.attribute( "inline_alias" ); + if ( str=="true" ) + option+="IA"; + else if ( str=="false" ) + option+="NIA"; + else + kdDebug()<<" Error in parsing inline_alias attribute :"<<str<<endl; + } + if( !option.isEmpty()) + { + option = option.prepend(":O"); + } + return option; + +} + +static QStringList parseLayoutNode(const QDomElement &docElem) +{ + QStringList layout; + + QString optionDefaultLayout; + if( docElem.tagName()=="DefaultLayout") + optionDefaultLayout = parseAttribute( docElem); + if ( !optionDefaultLayout.isEmpty() ) + layout.append( optionDefaultLayout ); + + QDomNode n = docElem.firstChild(); + while( !n.isNull() ) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (e.tagName() == "Separator") + { + layout.append(":S"); + } + else if (e.tagName() == "Filename") + { + layout.append(e.text()); + } + else if (e.tagName() == "Menuname") + { + layout.append("/"+e.text()); + QString option = parseAttribute( e ); + if( !option.isEmpty()) + layout.append( option ); + } + else if (e.tagName() == "Merge") + { + QString type = e.attributeNode("type").value(); + if (type == "files") + layout.append(":F"); + else if (type == "menus") + layout.append(":M"); + else if (type == "all") + layout.append(":A"); + } + + n = n.nextSibling(); + } + return layout; +} + +void +VFolderMenu::layoutMenu(VFolderMenu::SubMenu *menu, QStringList defaultLayout) +{ + if (!menu->defaultLayoutNode.isNull()) + { + defaultLayout = parseLayoutNode(menu->defaultLayoutNode); + } + + if (menu->layoutNode.isNull()) + { + menu->layoutList = defaultLayout; + } + else + { + menu->layoutList = parseLayoutNode(menu->layoutNode); + if (menu->layoutList.isEmpty()) + menu->layoutList = defaultLayout; + } + + for(VFolderMenu::SubMenu *subMenu = menu->subMenus.first(); subMenu; subMenu = menu->subMenus.next()) + { + layoutMenu(subMenu, defaultLayout); + } +} + +void +VFolderMenu::markUsedApplications(QDict<KService> *items) +{ + for(QDictIterator<KService> it(*items); it.current(); ++it) + { + m_usedAppsDict.replace(it.current()->menuId(), it.current()); + } +} + +VFolderMenu::SubMenu * +VFolderMenu::parseMenu(const QString &file, bool forceLegacyLoad) +{ + m_forcedLegacyLoad = false; + m_legacyLoaded = false; + m_appsInfo = 0; + + QStringList dirs = KGlobal::dirs()->resourceDirs("xdgconf-menu"); + for(QStringList::ConstIterator it=dirs.begin(); + it != dirs.end(); ++it) + { + registerDirectory(*it); + } + + loadMenu(file); + + delete m_rootMenu; + m_rootMenu = m_currentMenu = 0; + + QDomElement docElem = m_doc.documentElement(); + + for (int pass = 0; pass <= 2; pass++) + { + processMenu(docElem, pass); + + if (pass == 0) + { + buildApplicationIndex(false); + } + if (pass == 1) + { + buildApplicationIndex(true); + } + if (pass == 2) + { + QStringList defaultLayout; + defaultLayout << ":M"; // Sub-Menus + defaultLayout << ":F"; // Individual entries + layoutMenu(m_rootMenu, defaultLayout); + } + } + + if (!m_legacyLoaded && forceLegacyLoad) + { + m_forcedLegacyLoad = true; + processKDELegacyDirs(); + } + + return m_rootMenu; +} + +void +VFolderMenu::setTrackId(const QString &id) +{ + m_track = !id.isEmpty(); + m_trackId = id; +} + +#include "vfolder_menu.moc" |