You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
438 lines
14 KiB
438 lines
14 KiB
9 years ago
|
/* BEGIN_COMMON_COPYRIGHT_HEADER
|
||
|
* (c)LGPL2+
|
||
|
*
|
||
|
* Razor - a lightweight, Qt based, desktop toolset
|
||
|
* http://razor-qt.org
|
||
|
*
|
||
|
* Copyright: 2010-2011 Razor team
|
||
|
* Authors:
|
||
|
* Alexander Sokoloff <sokoloff.a@gmail.com>
|
||
|
*
|
||
|
* This program or library is free software; you can redistribute it
|
||
|
* and/or modify it under the terms of the GNU Lesser General Public
|
||
|
* License as published by the Free Software Foundation; either
|
||
|
* version 2.1 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* 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
|
||
|
* Lesser General Public License for more details.
|
||
|
|
||
|
* You should have received a copy of the GNU Lesser General
|
||
|
* Public License along with this library; if not, write to the
|
||
|
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||
|
* Boston, MA 02110-1301 USA
|
||
|
*
|
||
|
* END_COMMON_COPYRIGHT_HEADER */
|
||
|
|
||
|
#include "xdgmenureader.h"
|
||
|
#include "xdgmenu.h"
|
||
|
#include "xdgdirs.h"
|
||
|
#include "xmlhelper.h"
|
||
|
|
||
9 years ago
|
#include <QDebug>
|
||
|
#include <QDir>
|
||
9 years ago
|
#include <QFile>
|
||
|
#include <QFileInfo>
|
||
|
#include <QString>
|
||
|
#include <QtXml/QDomNamedNodeMap>
|
||
|
#include <QtXml/QDomNode>
|
||
|
|
||
|
|
||
|
XdgMenuReader::XdgMenuReader(XdgMenu* menu, XdgMenuReader* parentReader, QObject *parent) :
|
||
|
QObject(parent),
|
||
|
mMenu(menu)
|
||
|
{
|
||
|
mParentReader = parentReader;
|
||
|
if (mParentReader)
|
||
|
mBranchFiles << mParentReader->mBranchFiles;
|
||
|
}
|
||
|
|
||
|
|
||
|
XdgMenuReader::~XdgMenuReader()
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
bool XdgMenuReader::load(const QString& fileName, const QString& baseDir)
|
||
|
{
|
||
|
if (fileName.isEmpty())
|
||
|
{
|
||
|
mErrorStr = QLatin1String("Menu file not defined.");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
QFileInfo fileInfo(QDir(baseDir), fileName);
|
||
|
|
||
|
mFileName = fileInfo.canonicalFilePath();
|
||
|
mDirName = fileInfo.canonicalPath();
|
||
|
|
||
|
if (mBranchFiles.contains(mFileName))
|
||
|
return false; // Recursive loop detected
|
||
|
|
||
|
mBranchFiles << mFileName;
|
||
|
|
||
|
QFile file(mFileName);
|
||
|
if (!file.open(QFile::ReadOnly | QFile::Text))
|
||
|
{
|
||
8 years ago
|
mErrorStr = QString::fromLatin1("%1 not loading: %2").arg(fileName).arg(file.errorString());
|
||
9 years ago
|
return false;
|
||
|
}
|
||
|
//qDebug() << "Load file:" << mFileName;
|
||
|
mMenu->addWatchPath(mFileName);
|
||
|
|
||
|
QString errorStr;
|
||
|
int errorLine;
|
||
|
int errorColumn;
|
||
|
|
||
|
if (!mXml.setContent(&file, true, &errorStr, &errorLine, &errorColumn))
|
||
|
{
|
||
8 years ago
|
mErrorStr = QString::fromLatin1("Parse error at line %1, column %2:\n%3")
|
||
9 years ago
|
.arg(errorLine)
|
||
|
.arg(errorColumn)
|
||
|
.arg(errorStr);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
QDomElement root = mXml.documentElement();
|
||
|
|
||
8 years ago
|
QDomElement debugElement = mXml.createElement(QLatin1String("FileInfo"));
|
||
|
debugElement.setAttribute(QLatin1String("file"), mFileName);
|
||
9 years ago
|
if (mParentReader)
|
||
8 years ago
|
debugElement.setAttribute(QLatin1String("parent"), mParentReader->fileName());
|
||
9 years ago
|
|
||
|
QDomNode null;
|
||
|
root.insertBefore(debugElement, null);
|
||
|
|
||
|
processMergeTags(root);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
/************************************************
|
||
|
Duplicate <MergeXXX> elements (that specify the same file) are handled as with
|
||
|
duplicate <AppDir> elements (the last duplicate is used).
|
||
|
************************************************/
|
||
|
void XdgMenuReader::processMergeTags(QDomElement& element)
|
||
|
{
|
||
|
QDomElement n = element.lastChildElement();
|
||
|
QStringList mergedFiles;
|
||
|
|
||
|
|
||
|
while (!n.isNull())
|
||
|
{
|
||
|
QDomElement next = n.previousSiblingElement();
|
||
|
// MergeFile ..................
|
||
8 years ago
|
if (n.tagName() == QLatin1String("MergeFile"))
|
||
9 years ago
|
{
|
||
|
processMergeFileTag(n, &mergedFiles);
|
||
|
n.parentNode().removeChild(n);
|
||
|
}
|
||
|
|
||
|
// MergeDir ...................
|
||
8 years ago
|
else if(n.tagName() == QLatin1String("MergeDir"))
|
||
9 years ago
|
{
|
||
|
processMergeDirTag(n, &mergedFiles);
|
||
|
n.parentNode().removeChild(n);
|
||
|
}
|
||
|
|
||
|
// DefaultMergeDirs ...........
|
||
8 years ago
|
else if (n.tagName() == QLatin1String("DefaultMergeDirs"))
|
||
9 years ago
|
{
|
||
|
processDefaultMergeDirsTag(n, &mergedFiles);
|
||
|
n.parentNode().removeChild(n);
|
||
|
}
|
||
|
|
||
|
// AppDir ...................
|
||
8 years ago
|
else if(n.tagName() == QLatin1String("AppDir"))
|
||
9 years ago
|
{
|
||
|
processAppDirTag(n);
|
||
|
n.parentNode().removeChild(n);
|
||
|
}
|
||
|
|
||
|
// DefaultAppDirs .............
|
||
8 years ago
|
else if(n.tagName() == QLatin1String("DefaultAppDirs"))
|
||
9 years ago
|
{
|
||
|
processDefaultAppDirsTag(n);
|
||
|
n.parentNode().removeChild(n);
|
||
|
}
|
||
|
|
||
|
// DirectoryDir ...................
|
||
8 years ago
|
else if(n.tagName() == QLatin1String("DirectoryDir"))
|
||
9 years ago
|
{
|
||
|
processDirectoryDirTag(n);
|
||
|
n.parentNode().removeChild(n);
|
||
|
}
|
||
|
|
||
|
// DefaultDirectoryDirs ...........
|
||
8 years ago
|
else if(n.tagName() == QLatin1String("DefaultDirectoryDirs"))
|
||
9 years ago
|
{
|
||
|
processDefaultDirectoryDirsTag(n);
|
||
|
n.parentNode().removeChild(n);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Menu .......................
|
||
8 years ago
|
else if(n.tagName() == QLatin1String("Menu"))
|
||
9 years ago
|
{
|
||
|
processMergeTags(n);
|
||
|
}
|
||
|
|
||
|
n = next;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/************************************************
|
||
|
Any number of <MergeFile> elements may be listed below a <Menu> element, giving
|
||
|
the name of another menu file to be merged into this one.
|
||
|
If fileName is not an absolute path then the file to be merged should be located
|
||
|
relative to the location of this menu.
|
||
|
|
||
|
If the type attribute is missing or set to "path" then the contents of the
|
||
|
<MergeFile> element indicates the file to be merged.
|
||
|
|
||
|
If the type attribute is set to "parent" and the file that contains this
|
||
|
<MergeFile> element is located under one of the paths specified by
|
||
|
$XDG_CONFIG_DIRS, the contents of the element should be ignored and the remaining
|
||
|
paths specified by $XDG_CONFIG_DIRS are searched for a file with the same relative
|
||
|
filename. The first file encountered should be merged. There should be no merging
|
||
|
at all if no matching file is found. ( Libmenu additional scans ~/.config/menus.)
|
||
|
************************************************/
|
||
|
void XdgMenuReader::processMergeFileTag(QDomElement& element, QStringList* mergedFiles)
|
||
|
{
|
||
|
//qDebug() << "Process " << element;// << "in" << mFileName;
|
||
|
|
||
8 years ago
|
if (element.attribute(QLatin1String("type")) != QLatin1String("parent"))
|
||
9 years ago
|
{
|
||
|
mergeFile(element.text(), element, mergedFiles);
|
||
|
}
|
||
|
|
||
|
else
|
||
|
{
|
||
|
QString relativeName;
|
||
|
QStringList configDirs = XdgDirs::configDirs();
|
||
|
|
||
8 years ago
|
foreach (const QString &configDir, configDirs)
|
||
9 years ago
|
{
|
||
|
if (mFileName.startsWith(configDir))
|
||
|
{
|
||
|
relativeName = mFileName.mid(configDir.length());
|
||
|
configDirs.removeAll(configDir);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (relativeName.isEmpty())
|
||
|
{
|
||
|
QString configHome = XdgDirs::configHome();
|
||
|
if (mFileName.startsWith(configHome))
|
||
|
relativeName = mFileName.mid(configHome.length());
|
||
|
}
|
||
|
|
||
|
if (relativeName.isEmpty())
|
||
|
return;
|
||
|
|
||
8 years ago
|
foreach (const QString &configDir, configDirs)
|
||
9 years ago
|
{
|
||
|
if (QFileInfo(configDir + relativeName).exists())
|
||
|
{
|
||
|
mergeFile(configDir + relativeName, element, mergedFiles);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/************************************************
|
||
|
A <MergeDir> contains the name of a directory. Each file in the given directory
|
||
|
which ends in the ".menu" extension should be merged in the same way that a
|
||
|
<MergeFile> would be. If the filename given as a <MergeDir> is not an absolute
|
||
|
path, it should be located relative to the location of the menu file being parsed.
|
||
|
The files inside the merged directory are not merged in any specified order.
|
||
|
|
||
|
Duplicate <MergeDir> elements (that specify the same directory) are handled as with
|
||
|
duplicate <AppDir> elements (the last duplicate is used).
|
||
|
|
||
|
KDE additional scans ~/.config/menus.
|
||
|
************************************************/
|
||
|
void XdgMenuReader::processMergeDirTag(QDomElement& element, QStringList* mergedFiles)
|
||
|
{
|
||
|
//qDebug() << "Process " << element;// << "in" << mFileName;
|
||
|
|
||
|
mergeDir(element.text(), element, mergedFiles);
|
||
|
element.parentNode().removeChild(element);
|
||
|
}
|
||
|
|
||
|
|
||
|
/************************************************
|
||
|
The element has no content. The element should be treated as if it were a list of
|
||
|
<MergeDir> elements containing the default merge directory locations. When expanding
|
||
|
<DefaultMergeDirs> to a list of <MergeDir>, the default locations that are earlier
|
||
|
in the search path go later in the <Menu> so that they have priority.
|
||
|
|
||
|
Note that a system that uses either gnome-applications.menu or kde-applications.menu
|
||
|
depending on the desktop environment in use must still use applications-merged as the
|
||
|
default merge directory in both cases.
|
||
|
|
||
|
Implementations may chose to use .menu files with names other than application.menu
|
||
|
for tasks or menus other than the main application menu. In that case the first part
|
||
|
of the name of the default merge directory is derived from the name of the .menu file.
|
||
|
************************************************/
|
||
|
void XdgMenuReader::processDefaultMergeDirsTag(QDomElement& element, QStringList* mergedFiles)
|
||
|
{
|
||
|
//qDebug() << "Process " << element;// << "in" << mFileName;
|
||
|
|
||
|
QString menuBaseName = QFileInfo(mMenu->menuFileName()).baseName();
|
||
8 years ago
|
int n = menuBaseName.lastIndexOf(QLatin1Char('-'));
|
||
9 years ago
|
if (n>-1)
|
||
|
menuBaseName = menuBaseName.mid(n+1);
|
||
|
|
||
|
QStringList dirs = XdgDirs::configDirs();
|
||
|
dirs << XdgDirs::configHome();
|
||
|
|
||
8 years ago
|
foreach (const QString &dir, dirs)
|
||
9 years ago
|
{
|
||
8 years ago
|
mergeDir(QString::fromLatin1("%1/menus/%2-merged").arg(dir).arg(menuBaseName), element, mergedFiles);
|
||
9 years ago
|
}
|
||
|
|
||
8 years ago
|
if (menuBaseName == QLatin1String("applications"))
|
||
|
mergeFile(QString::fromLatin1("%1/menus/applications-kmenuedit.menu").arg(XdgDirs::configHome()), element, mergedFiles);
|
||
9 years ago
|
}
|
||
|
|
||
|
|
||
|
/************************************************
|
||
|
If the filename given as an <AppDir> is not an absolute path, it should be located
|
||
|
relative to the location of the menu file being parsed.
|
||
|
************************************************/
|
||
|
void XdgMenuReader::processAppDirTag(QDomElement& element)
|
||
|
{
|
||
|
//qDebug() << "Process " << element;
|
||
8 years ago
|
addDirTag(element, QLatin1String("AppDir"), element.text());
|
||
9 years ago
|
}
|
||
|
|
||
|
|
||
|
/************************************************
|
||
|
The element has no content. The element should be treated as if it were a list of
|
||
|
<AppDir> elements containing the default app dir locations
|
||
|
($XDG_DATA_DIRS/applications/).
|
||
|
|
||
|
menu-cache additional prepends $XDG_DATA_HOME/applications.
|
||
|
************************************************/
|
||
|
void XdgMenuReader::processDefaultAppDirsTag(QDomElement& element)
|
||
|
{
|
||
|
//qDebug() << "Process " << element;
|
||
|
QStringList dirs = XdgDirs::dataDirs();
|
||
|
dirs.prepend(XdgDirs::dataHome(false));
|
||
|
|
||
8 years ago
|
foreach (const QString &dir, dirs)
|
||
9 years ago
|
{
|
||
|
//qDebug() << "Add AppDir: " << dir + "/applications/";
|
||
8 years ago
|
addDirTag(element, QLatin1String("AppDir"), dir + QLatin1String("/applications/"));
|
||
9 years ago
|
}
|
||
|
}
|
||
|
|
||
|
/************************************************
|
||
|
If the filename given as a <DirectoryDir> is not an absolute path, it should be
|
||
|
located relative to the location of the menu file being parsed.
|
||
|
************************************************/
|
||
|
void XdgMenuReader::processDirectoryDirTag(QDomElement& element)
|
||
|
{
|
||
|
//qDebug() << "Process " << element;
|
||
8 years ago
|
addDirTag(element, QLatin1String("DirectoryDir"), element.text());
|
||
9 years ago
|
}
|
||
|
|
||
|
|
||
|
/************************************************
|
||
|
The element has no content. The element should be treated as if it were a list of
|
||
|
<DirectoryDir> elements containing the default desktop dir locations
|
||
|
($XDG_DATA_DIRS/desktop-directories/).
|
||
|
|
||
|
menu-cache additional prepends $XDG_DATA_HOME/applications.
|
||
|
************************************************/
|
||
|
void XdgMenuReader::processDefaultDirectoryDirsTag(QDomElement& element)
|
||
|
{
|
||
|
//qDebug() << "Process " << element;
|
||
|
QStringList dirs = XdgDirs::dataDirs();
|
||
|
dirs.prepend(XdgDirs::dataHome(false));
|
||
|
|
||
8 years ago
|
foreach (const QString &dir, dirs)
|
||
|
addDirTag(element, QLatin1String("DirectoryDir"), dir + QLatin1String("/desktop-directories/"));
|
||
9 years ago
|
}
|
||
|
|
||
|
/************************************************
|
||
|
|
||
|
************************************************/
|
||
|
void XdgMenuReader::addDirTag(QDomElement& previousElement, const QString& tagName, const QString& dir)
|
||
|
{
|
||
|
QFileInfo dirInfo(mDirName, dir);
|
||
|
if (dirInfo.isDir())
|
||
|
{
|
||
|
// qDebug() << "\tAdding " + dirInfo.canonicalFilePath();
|
||
|
QDomElement element = mXml.createElement(tagName);
|
||
|
element.appendChild(mXml.createTextNode(dirInfo.canonicalFilePath()));
|
||
|
previousElement.parentNode().insertBefore(element, previousElement);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/************************************************
|
||
|
If fileName is not an absolute path then the file to be merged should be located
|
||
|
relative to the location of this menu file.
|
||
|
************************************************/
|
||
|
void XdgMenuReader::mergeFile(const QString& fileName, QDomElement& element, QStringList* mergedFiles)
|
||
|
{
|
||
|
XdgMenuReader reader(mMenu, this);
|
||
|
QFileInfo fileInfo(QDir(mDirName), fileName);
|
||
|
|
||
|
if (!fileInfo.exists())
|
||
|
return;
|
||
|
|
||
|
if (mergedFiles->contains(fileInfo.canonicalFilePath()))
|
||
|
{
|
||
|
//qDebug() << "\tSkip: allredy merged";
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//qDebug() << "Merge file: " << fileName;
|
||
|
mergedFiles->append(fileInfo.canonicalFilePath());
|
||
|
|
||
|
if (reader.load(fileName, mDirName))
|
||
|
{
|
||
|
//qDebug() << "\tOK";
|
||
|
QDomElement n = reader.xml().firstChildElement().firstChildElement();
|
||
|
while (!n.isNull())
|
||
|
{
|
||
|
// As a special exception, remove the <Name> element from the root
|
||
|
// element of each file being merged.
|
||
8 years ago
|
if (n.tagName() != QLatin1String("Name"))
|
||
9 years ago
|
{
|
||
|
QDomNode imp = mXml.importNode(n, true);
|
||
|
element.parentNode().insertBefore(imp, element);
|
||
|
}
|
||
|
|
||
|
n = n.nextSiblingElement();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void XdgMenuReader::mergeDir(const QString& dirName, QDomElement& element, QStringList* mergedFiles)
|
||
|
{
|
||
|
QFileInfo dirInfo(mDirName, dirName);
|
||
|
|
||
|
if (dirInfo.isDir())
|
||
|
{
|
||
|
//qDebug() << "Merge dir: " << dirInfo.canonicalFilePath();
|
||
|
QDir dir = QDir(dirInfo.canonicalFilePath());
|
||
8 years ago
|
const QFileInfoList files = dir.entryInfoList(QStringList() << QLatin1String("*.menu"), QDir::Files | QDir::Readable);
|
||
9 years ago
|
|
||
8 years ago
|
foreach (const QFileInfo &file, files)
|
||
9 years ago
|
mergeFile(file.canonicalFilePath(), element, mergedFiles);
|
||
|
}
|
||
|
}
|