Fixed libqt5xdgiconloader-dev.install Reworked descriptions in debian/control to prevent lintian whining Fixed symbols version Fixed copyright for xdgiconloader Fixed package descrioptions Introduced the new package libqt5xdgiconloader1 Bumped standards to 3.9.8, no changes needed Symbols sorted and unified Bump years in copyright Added hardening=+all Added README.md to debian/docs set CMAKE_BUILD_TYPE=RelWithDebInfo
412 lines
13 KiB
C++
412 lines
13 KiB
C++
/* 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 "xdgmenulayoutprocessor.h"
|
|
#include "xmlhelper.h"
|
|
#include <QDebug>
|
|
#include <QMap>
|
|
|
|
|
|
// Helper functions prototypes
|
|
QDomElement findLastElementByTag(const QDomElement element, const QString tagName);
|
|
int childsCount(const QDomElement& element);
|
|
|
|
|
|
QDomElement findLastElementByTag(const QDomElement element, const QString tagName)
|
|
{
|
|
QDomNodeList l = element.elementsByTagName(tagName);
|
|
if (l.isEmpty())
|
|
return QDomElement();
|
|
|
|
return l.at(l.length()-1).toElement();
|
|
}
|
|
|
|
|
|
/************************************************
|
|
If no default-layout has been specified then the layout as specified by
|
|
the following elements should be assumed:
|
|
<DefaultLayout
|
|
show_empty="false"
|
|
inline="false"
|
|
inline_limit="4"
|
|
inline_header="true"
|
|
inline_alias="false">
|
|
<Merge type="menus"/>
|
|
<Merge type="files"/>
|
|
</DefaultLayout>
|
|
************************************************/
|
|
XdgMenuLayoutProcessor::XdgMenuLayoutProcessor(QDomElement& element):
|
|
mElement(element)
|
|
{
|
|
mDefaultParams.mShowEmpty = false;
|
|
mDefaultParams.mInline = false;
|
|
mDefaultParams.mInlineLimit = 4;
|
|
mDefaultParams.mInlineHeader = true;
|
|
mDefaultParams.mInlineAlias = false;
|
|
|
|
mDefaultLayout = findLastElementByTag(element, QLatin1String("DefaultLayout"));
|
|
|
|
if (mDefaultLayout.isNull())
|
|
{
|
|
// Create DefaultLayout node
|
|
QDomDocument doc = element.ownerDocument();
|
|
mDefaultLayout = doc.createElement(QLatin1String("DefaultLayout"));
|
|
|
|
QDomElement menus = doc.createElement(QLatin1String("Merge"));
|
|
menus.setAttribute(QLatin1String("type"), QLatin1String("menus"));
|
|
mDefaultLayout.appendChild(menus);
|
|
|
|
QDomElement files = doc.createElement(QLatin1String("Merge"));
|
|
files.setAttribute(QLatin1String("type"), QLatin1String("files"));
|
|
mDefaultLayout.appendChild(files);
|
|
|
|
mElement.appendChild(mDefaultLayout);
|
|
}
|
|
|
|
setParams(mDefaultLayout, &mDefaultParams);
|
|
|
|
// If a menu does not contain a <Layout> element or if it contains an empty <Layout> element
|
|
// then the default layout should be used.
|
|
mLayout = findLastElementByTag(element, QLatin1String("Layout"));
|
|
if (mLayout.isNull() || !mLayout.hasChildNodes())
|
|
mLayout = mDefaultLayout;
|
|
}
|
|
|
|
|
|
XdgMenuLayoutProcessor::XdgMenuLayoutProcessor(QDomElement& element, XdgMenuLayoutProcessor *parent):
|
|
mElement(element)
|
|
{
|
|
mDefaultParams = parent->mDefaultParams;
|
|
|
|
// DefaultLayout ............................
|
|
QDomElement defaultLayout = findLastElementByTag(element, QLatin1String("DefaultLayout"));
|
|
|
|
if (defaultLayout.isNull())
|
|
mDefaultLayout = parent->mDefaultLayout;
|
|
else
|
|
mDefaultLayout = defaultLayout;
|
|
|
|
setParams(mDefaultLayout, &mDefaultParams);
|
|
|
|
// If a menu does not contain a <Layout> element or if it contains an empty <Layout> element
|
|
// then the default layout should be used.
|
|
mLayout = findLastElementByTag(element, QLatin1String("Layout"));
|
|
if (mLayout.isNull() || !mLayout.hasChildNodes())
|
|
mLayout = mDefaultLayout;
|
|
|
|
}
|
|
|
|
|
|
void XdgMenuLayoutProcessor::setParams(QDomElement defaultLayout, LayoutParams *result)
|
|
{
|
|
if (defaultLayout.hasAttribute(QLatin1String("show_empty")))
|
|
result->mShowEmpty = defaultLayout.attribute(QLatin1String("show_empty")) == QLatin1String("true");
|
|
|
|
if (defaultLayout.hasAttribute(QLatin1String("inline")))
|
|
result->mInline = defaultLayout.attribute(QLatin1String("inline")) == QLatin1String("true");
|
|
|
|
if (defaultLayout.hasAttribute(QLatin1String("inline_limit")))
|
|
result->mInlineLimit = defaultLayout.attribute(QLatin1String("inline_limit")).toInt();
|
|
|
|
if (defaultLayout.hasAttribute(QLatin1String("inline_header")))
|
|
result->mInlineHeader = defaultLayout.attribute(QLatin1String("inline_header")) == QLatin1String("true");
|
|
|
|
if (defaultLayout.hasAttribute(QLatin1String("inline_alias")))
|
|
result->mInlineAlias = defaultLayout.attribute(QLatin1String("inline_alias")) == QLatin1String("true");
|
|
}
|
|
|
|
|
|
QDomElement XdgMenuLayoutProcessor::searchElement(const QString &tagName, const QString &attributeName, const QString &attributeValue) const
|
|
{
|
|
DomElementIterator it(mElement, tagName);
|
|
while (it.hasNext())
|
|
{
|
|
QDomElement e = it.next();
|
|
if (e.attribute(attributeName) == attributeValue)
|
|
{
|
|
return e;
|
|
}
|
|
}
|
|
|
|
return QDomElement();
|
|
}
|
|
|
|
|
|
int childsCount(const QDomElement& element)
|
|
{
|
|
int count = 0;
|
|
DomElementIterator it(element);
|
|
while (it.hasNext())
|
|
{
|
|
QString tag = it.next().tagName();
|
|
if (tag == QLatin1String("AppLink") || tag == QLatin1String("Menu") || tag == QLatin1String("Separator"))
|
|
count ++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
void XdgMenuLayoutProcessor::run()
|
|
{
|
|
QDomDocument doc = mLayout.ownerDocument();
|
|
mResult = doc.createElement(QLatin1String("Result"));
|
|
mElement.appendChild(mResult);
|
|
|
|
// Process childs menus ...............................
|
|
{
|
|
DomElementIterator it(mElement, QLatin1String("Menu"));
|
|
while (it.hasNext())
|
|
{
|
|
QDomElement e = it.next();
|
|
XdgMenuLayoutProcessor p(e, this);
|
|
p.run();
|
|
}
|
|
}
|
|
|
|
|
|
// Step 1 ...................................
|
|
DomElementIterator it(mLayout);
|
|
it.toFront();
|
|
while (it.hasNext())
|
|
{
|
|
QDomElement e = it.next();
|
|
|
|
if (e.tagName() == QLatin1String("Filename"))
|
|
processFilenameTag(e);
|
|
|
|
else if (e.tagName() == QLatin1String("Menuname"))
|
|
processMenunameTag(e);
|
|
|
|
else if (e.tagName() == QLatin1String("Separator"))
|
|
processSeparatorTag(e);
|
|
|
|
else if (e.tagName() == QLatin1String("Merge"))
|
|
{
|
|
QDomElement merge = mResult.ownerDocument().createElement(QLatin1String("Merge"));
|
|
merge.setAttribute(QLatin1String("type"), e.attribute(QLatin1String("type")));
|
|
mResult.appendChild(merge);
|
|
}
|
|
}
|
|
|
|
// Step 2 ...................................
|
|
{
|
|
MutableDomElementIterator ri(mResult, QLatin1String("Merge"));
|
|
while (ri.hasNext())
|
|
{
|
|
processMergeTag(ri.next());
|
|
}
|
|
}
|
|
|
|
// Move result cilds to element .............
|
|
MutableDomElementIterator ri(mResult);
|
|
while (ri.hasNext())
|
|
{
|
|
mElement.appendChild(ri.next());
|
|
}
|
|
|
|
// Final ....................................
|
|
mElement.removeChild(mResult);
|
|
|
|
if (mLayout.parentNode() == mElement)
|
|
mElement.removeChild(mLayout);
|
|
|
|
if (mDefaultLayout.parentNode() == mElement)
|
|
mElement.removeChild(mDefaultLayout);
|
|
|
|
}
|
|
|
|
|
|
/************************************************
|
|
The <Filename> element is the most basic matching rule.
|
|
It matches a desktop entry if the desktop entry has the given desktop-file id
|
|
************************************************/
|
|
void XdgMenuLayoutProcessor::processFilenameTag(const QDomElement &element)
|
|
{
|
|
QString id = element.text();
|
|
|
|
QDomElement appLink = searchElement(QLatin1String("AppLink"), QLatin1String("id"), id);
|
|
if (!appLink.isNull())
|
|
mResult.appendChild(appLink);
|
|
}
|
|
|
|
|
|
/************************************************
|
|
Its contents references an immediate sub-menu of the current menu, as such it should never contain
|
|
a slash. If no such sub-menu exists the element should be ignored.
|
|
This element may have various attributes, the default values are taken from the DefaultLayout key.
|
|
|
|
show_empty [ bool ]
|
|
defines whether a menu that contains no desktop entries and no sub-menus
|
|
should be shown at all.
|
|
|
|
inline [ bool ]
|
|
If the inline attribute is "true" the menu that is referenced may be copied into the current
|
|
menu at the current point instead of being inserted as sub-menu of the current menu.
|
|
|
|
inline_limit [ int ]
|
|
defines the maximum number of entries that can be inlined. If the sub-menu has more entries
|
|
than inline_limit, the sub-menu will not be inlined. If the inline_limit is 0 (zero) there
|
|
is no limit.
|
|
|
|
inline_header [ bool ]
|
|
defines whether an inlined menu should be preceded with a header entry listing the caption of
|
|
the sub-menu.
|
|
|
|
inline_alias [ bool ]
|
|
defines whether a single inlined entry should adopt the caption of the inlined menu. In such
|
|
case no additional header entry will be added regardless of the value of the inline_header
|
|
attribute.
|
|
|
|
Example: if a menu has a sub-menu titled "WordProcessor" with a single entry "OpenOffice 4.2", and
|
|
both inline="true" and inline_alias="true" are specified then this would result in the
|
|
"OpenOffice 4.2" entry being inlined in the current menu but the "OpenOffice 4.2" caption of the
|
|
entry would be replaced with "WordProcessor".
|
|
************************************************/
|
|
void XdgMenuLayoutProcessor::processMenunameTag(const QDomElement &element)
|
|
{
|
|
QString id = element.text();
|
|
QDomElement menu = searchElement(QLatin1String("Menu"), QLatin1String("name"), id);
|
|
if (menu.isNull())
|
|
return;
|
|
|
|
LayoutParams params = mDefaultParams;
|
|
setParams(element, ¶ms);
|
|
|
|
int count = childsCount(menu);
|
|
|
|
if (count == 0)
|
|
{
|
|
if (params.mShowEmpty)
|
|
{
|
|
menu.setAttribute(QLatin1String("keep"), QLatin1String("true"));
|
|
mResult.appendChild(menu);
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
bool doInline = params.mInline &&
|
|
(!params.mInlineLimit || params.mInlineLimit > count);
|
|
|
|
bool doAlias = params.mInlineAlias &&
|
|
doInline && (count == 1);
|
|
|
|
bool doHeader = params.mInlineHeader &&
|
|
doInline && !doAlias;
|
|
|
|
|
|
if (!doInline)
|
|
{
|
|
mResult.appendChild(menu);
|
|
return;
|
|
}
|
|
|
|
|
|
// Header ....................................
|
|
if (doHeader)
|
|
{
|
|
QDomElement header = mLayout.ownerDocument().createElement(QLatin1String("Header"));
|
|
|
|
QDomNamedNodeMap attrs = menu.attributes();
|
|
for (int i=0; i < attrs.count(); ++i)
|
|
{
|
|
header.setAttributeNode(attrs.item(i).toAttr());
|
|
}
|
|
|
|
mResult.appendChild(header);
|
|
}
|
|
|
|
// Alias .....................................
|
|
if (doAlias)
|
|
{
|
|
menu.firstChild().toElement().setAttribute(QLatin1String("title"), menu.attribute(QLatin1String("title")));
|
|
}
|
|
|
|
// Inline ....................................
|
|
MutableDomElementIterator it(menu);
|
|
while (it.hasNext())
|
|
{
|
|
mResult.appendChild(it.next());
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/************************************************
|
|
It indicates a suggestion to draw a visual separator at this point in the menu.
|
|
<Separator> elements at the start of a menu, at the end of a menu or that directly
|
|
follow other <Separator> elements may be ignored.
|
|
************************************************/
|
|
void XdgMenuLayoutProcessor::processSeparatorTag(const QDomElement &element)
|
|
{
|
|
QDomElement separator = element.ownerDocument().createElement(QLatin1String("Separator"));
|
|
mResult.appendChild(separator);
|
|
}
|
|
|
|
|
|
/************************************************
|
|
It indicates the point where desktop entries and sub-menus that are not explicitly mentioned
|
|
within the <Layout> or <DefaultLayout> element are to be inserted.
|
|
It has a type attribute that indicates which elements should be inserted:
|
|
|
|
type="menus"
|
|
means that all sub-menus that are not explicitly mentioned should be inserted in alphabetical
|
|
order of their visual caption at this point.
|
|
|
|
type="files" means that all desktop entries contained in this menu that are not explicitly
|
|
mentioned should be inserted in alphabetical order of their visual caption at this point.
|
|
|
|
type="all" means that a mix of all sub-menus and all desktop entries that are not explicitly
|
|
mentioned should be inserted in alphabetical order of their visual caption at this point.
|
|
|
|
************************************************/
|
|
void XdgMenuLayoutProcessor::processMergeTag(const QDomElement &element)
|
|
{
|
|
QString type = element.attribute(QLatin1String("type"));
|
|
QMap<QString, QDomElement> map;
|
|
MutableDomElementIterator it(mElement);
|
|
|
|
while (it.hasNext())
|
|
{
|
|
QDomElement e = it.next();
|
|
if (
|
|
((type == QLatin1String("menus") || type == QLatin1String("all")) && e.tagName() == QLatin1String("Menu" )) ||
|
|
((type == QLatin1String("files") || type == QLatin1String("all")) && e.tagName() == QLatin1String("AppLink"))
|
|
)
|
|
map.insert(e.attribute(QLatin1String("title")), e);
|
|
}
|
|
|
|
QMapIterator<QString, QDomElement> mi(map);
|
|
while (mi.hasNext()) {
|
|
mi.next();
|
|
mResult.insertBefore(mi.value(), element);
|
|
}
|
|
|
|
mResult.removeChild(element);
|
|
}
|