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.
553 lines
14 KiB
553 lines
14 KiB
/*
|
|
* Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include <tqfile.h>
|
|
#include <tqtextstream.h>
|
|
#include <tqregexp.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <tdeglobal.h>
|
|
#include <tdelocale.h>
|
|
#include <kstandarddirs.h>
|
|
|
|
#include "menufile.h"
|
|
|
|
|
|
#define MF_MENU "Menu"
|
|
#define MF_PUBLIC_ID "-//freedesktop//DTD Menu 1.0//EN"
|
|
#define MF_SYSTEM_ID "http://www.freedesktop.org/standards/menu-spec/1.0/menu.dtd"
|
|
#define MF_NAME "Name"
|
|
#define MF_INCLUDE "Include"
|
|
#define MF_EXCLUDE "Exclude"
|
|
#define MF_FILENAME "Filename"
|
|
#define MF_DELETED "Deleted"
|
|
#define MF_NOTDELETED "NotDeleted"
|
|
#define MF_MOVE "Move"
|
|
#define MF_OLD "Old"
|
|
#define MF_NEW "New"
|
|
#define MF_DIRECTORY "Directory"
|
|
#define MF_LAYOUT "Layout"
|
|
#define MF_MENUNAME "Menuname"
|
|
#define MF_SEPARATOR "Separator"
|
|
#define MF_MERGE "Merge"
|
|
|
|
MenuFile::MenuFile(const TQString &file)
|
|
: m_fileName(file), m_bDirty(false)
|
|
{
|
|
load();
|
|
}
|
|
|
|
MenuFile::~MenuFile()
|
|
{
|
|
}
|
|
|
|
bool MenuFile::load()
|
|
{
|
|
if (m_fileName.isEmpty())
|
|
return false;
|
|
|
|
TQFile file( m_fileName );
|
|
if (!file.open( IO_ReadOnly ))
|
|
{
|
|
kdWarning() << "Could not read " << m_fileName << endl;
|
|
create();
|
|
return false;
|
|
}
|
|
|
|
TQString errorMsg;
|
|
int errorRow;
|
|
int errorCol;
|
|
if ( !m_doc.setContent( &file, &errorMsg, &errorRow, &errorCol ) ) {
|
|
kdWarning() << "Parse error in " << m_fileName << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg << endl;
|
|
file.close();
|
|
create();
|
|
return false;
|
|
}
|
|
file.close();
|
|
|
|
return true;
|
|
}
|
|
|
|
void MenuFile::create()
|
|
{
|
|
TQDomImplementation impl;
|
|
TQDomDocumentType docType = impl.createDocumentType( MF_MENU, MF_PUBLIC_ID, MF_SYSTEM_ID );
|
|
m_doc = impl.createDocument(TQString::null, MF_MENU, docType);
|
|
}
|
|
|
|
bool MenuFile::save()
|
|
{
|
|
TQFile file( m_fileName );
|
|
|
|
if (!file.open( IO_WriteOnly ))
|
|
{
|
|
kdWarning() << "Could not write " << m_fileName << endl;
|
|
m_error = i18n("Could not write to %1").arg(m_fileName);
|
|
return false;
|
|
}
|
|
TQTextStream stream( &file );
|
|
stream.setEncoding(TQTextStream::UnicodeUTF8);
|
|
|
|
stream << m_doc.toString();
|
|
|
|
file.close();
|
|
|
|
if (file.status() != IO_Ok)
|
|
{
|
|
kdWarning() << "Could not close " << m_fileName << endl;
|
|
m_error = i18n("Could not write to %1").arg(m_fileName);
|
|
return false;
|
|
}
|
|
|
|
m_bDirty = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
TQDomElement MenuFile::findMenu(TQDomElement elem, const TQString &menuName, bool create)
|
|
{
|
|
TQString menuNodeName;
|
|
TQString subMenuName;
|
|
int i = menuName.find('/');
|
|
if (i >= 0)
|
|
{
|
|
menuNodeName = menuName.left(i);
|
|
subMenuName = menuName.mid(i+1);
|
|
}
|
|
else
|
|
{
|
|
menuNodeName = menuName;
|
|
}
|
|
if (i == 0)
|
|
return findMenu(elem, subMenuName, create);
|
|
|
|
if (menuNodeName.isEmpty())
|
|
return elem;
|
|
|
|
TQDomNode n = elem.firstChild();
|
|
while( !n.isNull() )
|
|
{
|
|
TQDomElement e = n.toElement(); // try to convert the node to an element.
|
|
if (e.tagName() == MF_MENU)
|
|
{
|
|
TQString name;
|
|
|
|
TQDomNode n2 = e.firstChild();
|
|
while ( !n2.isNull() )
|
|
{
|
|
TQDomElement e2 = n2.toElement();
|
|
if (!e2.isNull() && e2.tagName() == MF_NAME)
|
|
{
|
|
name = e2.text();
|
|
break;
|
|
}
|
|
n2 = n2.nextSibling();
|
|
}
|
|
|
|
if (name == menuNodeName)
|
|
{
|
|
if (subMenuName.isEmpty())
|
|
return e;
|
|
else
|
|
return findMenu(e, subMenuName, create);
|
|
}
|
|
}
|
|
n = n.nextSibling();
|
|
}
|
|
|
|
if (!create)
|
|
return TQDomElement();
|
|
|
|
// Create new node.
|
|
TQDomElement newElem = m_doc.createElement(MF_MENU);
|
|
TQDomElement newNameElem = m_doc.createElement(MF_NAME);
|
|
newNameElem.appendChild(m_doc.createTextNode(menuNodeName));
|
|
newElem.appendChild(newNameElem);
|
|
elem.appendChild(newElem);
|
|
|
|
if (subMenuName.isEmpty())
|
|
return newElem;
|
|
else
|
|
return findMenu(newElem, subMenuName, create);
|
|
}
|
|
|
|
static TQString entryToDirId(const TQString &path)
|
|
{
|
|
// See also KDesktopFile::locateLocal
|
|
TQString local;
|
|
if (path.startsWith("/"))
|
|
{
|
|
// XDG Desktop menu items come with absolute paths, we need to
|
|
// extract their relative path and then build a local path.
|
|
local = TDEGlobal::dirs()->relativeLocation("xdgdata-dirs", path);
|
|
}
|
|
|
|
if (local.isEmpty() || local.startsWith("/"))
|
|
{
|
|
// What now? Use filename only and hope for the best.
|
|
local = path.mid(path.findRev('/')+1);
|
|
}
|
|
return local;
|
|
}
|
|
|
|
static void purgeIncludesExcludes(TQDomElement elem, const TQString &appId, TQDomElement &excludeNode, TQDomElement &includeNode)
|
|
{
|
|
// Remove any previous includes/excludes of appId
|
|
TQDomNode n = elem.firstChild();
|
|
while( !n.isNull() )
|
|
{
|
|
TQDomElement e = n.toElement(); // try to convert the node to an element.
|
|
bool bIncludeNode = (e.tagName() == MF_INCLUDE);
|
|
bool bExcludeNode = (e.tagName() == MF_EXCLUDE);
|
|
if (bIncludeNode)
|
|
includeNode = e;
|
|
if (bExcludeNode)
|
|
excludeNode = e;
|
|
if (bIncludeNode || bExcludeNode)
|
|
{
|
|
TQDomNode n2 = e.firstChild();
|
|
while ( !n2.isNull() )
|
|
{
|
|
TQDomNode next = n2.nextSibling();
|
|
TQDomElement e2 = n2.toElement();
|
|
if (!e2.isNull() && e2.tagName() == MF_FILENAME)
|
|
{
|
|
if (e2.text() == appId)
|
|
{
|
|
e.removeChild(e2);
|
|
break;
|
|
}
|
|
}
|
|
n2 = next;
|
|
}
|
|
}
|
|
n = n.nextSibling();
|
|
}
|
|
}
|
|
|
|
static void purgeDeleted(TQDomElement elem)
|
|
{
|
|
// Remove any previous includes/excludes of appId
|
|
TQDomNode n = elem.firstChild();
|
|
while( !n.isNull() )
|
|
{
|
|
TQDomNode next = n.nextSibling();
|
|
TQDomElement e = n.toElement(); // try to convert the node to an element.
|
|
if ((e.tagName() == MF_DELETED) ||
|
|
(e.tagName() == MF_NOTDELETED))
|
|
{
|
|
elem.removeChild(e);
|
|
}
|
|
n = next;
|
|
}
|
|
}
|
|
|
|
static void purgeLayout(TQDomElement elem)
|
|
{
|
|
// Remove any previous includes/excludes of appId
|
|
TQDomNode n = elem.firstChild();
|
|
while( !n.isNull() )
|
|
{
|
|
TQDomNode next = n.nextSibling();
|
|
TQDomElement e = n.toElement(); // try to convert the node to an element.
|
|
if (e.tagName() == MF_LAYOUT)
|
|
{
|
|
elem.removeChild(e);
|
|
}
|
|
n = next;
|
|
}
|
|
}
|
|
|
|
void MenuFile::addEntry(const TQString &menuName, const TQString &menuId)
|
|
{
|
|
m_bDirty = true;
|
|
|
|
m_removedEntries.remove(menuId);
|
|
|
|
TQDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
|
|
|
|
TQDomElement excludeNode;
|
|
TQDomElement includeNode;
|
|
|
|
purgeIncludesExcludes(elem, menuId, excludeNode, includeNode);
|
|
|
|
if (includeNode.isNull())
|
|
{
|
|
includeNode = m_doc.createElement(MF_INCLUDE);
|
|
elem.appendChild(includeNode);
|
|
}
|
|
|
|
TQDomElement fileNode = m_doc.createElement(MF_FILENAME);
|
|
fileNode.appendChild(m_doc.createTextNode(menuId));
|
|
includeNode.appendChild(fileNode);
|
|
}
|
|
|
|
void MenuFile::setLayout(const TQString &menuName, const TQStringList &layout)
|
|
{
|
|
m_bDirty = true;
|
|
|
|
TQDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
|
|
|
|
purgeLayout(elem);
|
|
|
|
TQDomElement layoutNode = m_doc.createElement(MF_LAYOUT);
|
|
elem.appendChild(layoutNode);
|
|
|
|
for(TQStringList::ConstIterator it = layout.begin();
|
|
it != layout.end(); ++it)
|
|
{
|
|
TQString li = *it;
|
|
if (li == ":S")
|
|
{
|
|
layoutNode.appendChild(m_doc.createElement(MF_SEPARATOR));
|
|
}
|
|
else if (li == ":M")
|
|
{
|
|
TQDomElement mergeNode = m_doc.createElement(MF_MERGE);
|
|
mergeNode.setAttribute("type", "menus");
|
|
layoutNode.appendChild(mergeNode);
|
|
}
|
|
else if (li == ":F")
|
|
{
|
|
TQDomElement mergeNode = m_doc.createElement(MF_MERGE);
|
|
mergeNode.setAttribute("type", "files");
|
|
layoutNode.appendChild(mergeNode);
|
|
}
|
|
else if (li == ":A")
|
|
{
|
|
TQDomElement mergeNode = m_doc.createElement(MF_MERGE);
|
|
mergeNode.setAttribute("type", "all");
|
|
layoutNode.appendChild(mergeNode);
|
|
}
|
|
else if (li.endsWith("/"))
|
|
{
|
|
li.truncate(li.length()-1);
|
|
TQDomElement menuNode = m_doc.createElement(MF_MENUNAME);
|
|
menuNode.appendChild(m_doc.createTextNode(li));
|
|
layoutNode.appendChild(menuNode);
|
|
}
|
|
else
|
|
{
|
|
TQDomElement fileNode = m_doc.createElement(MF_FILENAME);
|
|
fileNode.appendChild(m_doc.createTextNode(li));
|
|
layoutNode.appendChild(fileNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void MenuFile::removeEntry(const TQString &menuName, const TQString &menuId)
|
|
{
|
|
m_bDirty = true;
|
|
|
|
m_removedEntries.append(menuId);
|
|
|
|
TQDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
|
|
|
|
TQDomElement excludeNode;
|
|
TQDomElement includeNode;
|
|
|
|
purgeIncludesExcludes(elem, menuId, excludeNode, includeNode);
|
|
|
|
if (excludeNode.isNull())
|
|
{
|
|
excludeNode = m_doc.createElement(MF_EXCLUDE);
|
|
elem.appendChild(excludeNode);
|
|
}
|
|
|
|
TQDomElement fileNode = m_doc.createElement(MF_FILENAME);
|
|
fileNode.appendChild(m_doc.createTextNode(menuId));
|
|
excludeNode.appendChild(fileNode);
|
|
}
|
|
|
|
void MenuFile::addMenu(const TQString &menuName, const TQString &menuFile)
|
|
{
|
|
m_bDirty = true;
|
|
TQDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
|
|
|
|
TQDomElement dirElem = m_doc.createElement(MF_DIRECTORY);
|
|
dirElem.appendChild(m_doc.createTextNode(entryToDirId(menuFile)));
|
|
elem.appendChild(dirElem);
|
|
}
|
|
|
|
void MenuFile::moveMenu(const TQString &oldMenu, const TQString &newMenu)
|
|
{
|
|
m_bDirty = true;
|
|
|
|
// Undelete the new menu
|
|
TQDomElement elem = findMenu(m_doc.documentElement(), newMenu, true);
|
|
purgeDeleted(elem);
|
|
elem.appendChild(m_doc.createElement(MF_NOTDELETED));
|
|
|
|
// TODO: GET RID OF COMMON PART, IT BREAKS STUFF
|
|
// Find common part
|
|
TQStringList oldMenuParts = TQStringList::split('/', oldMenu);
|
|
TQStringList newMenuParts = TQStringList::split('/', newMenu);
|
|
TQString commonMenuName;
|
|
uint max = TQMIN(oldMenuParts.count(), newMenuParts.count());
|
|
uint i = 0;
|
|
for(; i < max; i++)
|
|
{
|
|
if (oldMenuParts[i] != newMenuParts[i])
|
|
break;
|
|
commonMenuName += '/' + oldMenuParts[i];
|
|
}
|
|
TQString oldMenuName;
|
|
for(uint j = i; j < oldMenuParts.count(); j++)
|
|
{
|
|
if (i != j)
|
|
oldMenuName += '/';
|
|
oldMenuName += oldMenuParts[j];
|
|
}
|
|
TQString newMenuName;
|
|
for(uint j = i; j < newMenuParts.count(); j++)
|
|
{
|
|
if (i != j)
|
|
newMenuName += '/';
|
|
newMenuName += newMenuParts[j];
|
|
}
|
|
|
|
if (oldMenuName == newMenuName) return; // Can happen
|
|
|
|
elem = findMenu(m_doc.documentElement(), commonMenuName, true);
|
|
|
|
// Add instructions for moving
|
|
TQDomElement moveNode = m_doc.createElement(MF_MOVE);
|
|
TQDomElement node = m_doc.createElement(MF_OLD);
|
|
node.appendChild(m_doc.createTextNode(oldMenuName));
|
|
moveNode.appendChild(node);
|
|
node = m_doc.createElement(MF_NEW);
|
|
node.appendChild(m_doc.createTextNode(newMenuName));
|
|
moveNode.appendChild(node);
|
|
elem.appendChild(moveNode);
|
|
}
|
|
|
|
void MenuFile::removeMenu(const TQString &menuName)
|
|
{
|
|
m_bDirty = true;
|
|
|
|
TQDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
|
|
|
|
purgeDeleted(elem);
|
|
elem.appendChild(m_doc.createElement(MF_DELETED));
|
|
}
|
|
|
|
/**
|
|
* Returns a unique menu-name for a new menu under @p menuName
|
|
* inspired by @p newMenu
|
|
*/
|
|
TQString MenuFile::uniqueMenuName(const TQString &menuName, const TQString &newMenu, const TQStringList & excludeList)
|
|
{
|
|
TQDomElement elem = findMenu(m_doc.documentElement(), menuName, false);
|
|
|
|
TQString result = newMenu;
|
|
if (result.endsWith("/"))
|
|
result.truncate(result.length()-1);
|
|
|
|
TQRegExp r("(.*)(?=-\\d+)");
|
|
result = (r.search(result) > -1) ? r.cap(1) : result;
|
|
|
|
int trunc = result.length(); // Position of trailing '/'
|
|
|
|
result.append("/");
|
|
|
|
for(int n = 1; ++n; )
|
|
{
|
|
if (findMenu(elem, result, false).isNull() && !excludeList.contains(result))
|
|
return result;
|
|
|
|
result.truncate(trunc);
|
|
result.append(TQString("-%1/").arg(n));
|
|
}
|
|
return TQString::null; // Never reached
|
|
}
|
|
|
|
void MenuFile::performAction(const ActionAtom *atom)
|
|
{
|
|
switch(atom->action)
|
|
{
|
|
case ADD_ENTRY:
|
|
addEntry(atom->arg1, atom->arg2);
|
|
return;
|
|
case REMOVE_ENTRY:
|
|
removeEntry(atom->arg1, atom->arg2);
|
|
return;
|
|
case ADD_MENU:
|
|
addMenu(atom->arg1, atom->arg2);
|
|
return;
|
|
case REMOVE_MENU:
|
|
removeMenu(atom->arg1);
|
|
return;
|
|
case MOVE_MENU:
|
|
moveMenu(atom->arg1, atom->arg2);
|
|
return;
|
|
}
|
|
}
|
|
|
|
MenuFile::ActionAtom *MenuFile::pushAction(MenuFile::ActionType action, const TQString &arg1, const TQString &arg2)
|
|
{
|
|
ActionAtom *atom = new ActionAtom;
|
|
atom->action = action;
|
|
atom->arg1 = arg1;
|
|
atom->arg2 = arg2;
|
|
m_actionList.append(atom);
|
|
return atom;
|
|
}
|
|
|
|
void MenuFile::popAction(ActionAtom *atom)
|
|
{
|
|
if (m_actionList.getLast() != atom)
|
|
{
|
|
tqWarning("MenuFile::popAction Error, action not last in list.");
|
|
return;
|
|
}
|
|
m_actionList.removeLast();
|
|
delete atom;
|
|
}
|
|
|
|
bool MenuFile::performAllActions()
|
|
{
|
|
for(ActionAtom *atom; (atom = m_actionList.getFirst()); m_actionList.removeFirst())
|
|
{
|
|
performAction(atom);
|
|
delete atom;
|
|
}
|
|
|
|
// Entries that have been removed from the menu are added to .hidden
|
|
// so that they don't re-appear in Lost & Found
|
|
TQStringList removed = m_removedEntries;
|
|
m_removedEntries.clear();
|
|
for(TQStringList::ConstIterator it = removed.begin();
|
|
it != removed.end(); ++it)
|
|
{
|
|
addEntry("/.hidden/", *it);
|
|
}
|
|
|
|
m_removedEntries.clear();
|
|
|
|
if (!m_bDirty)
|
|
return true;
|
|
|
|
return save();
|
|
}
|
|
|
|
bool MenuFile::dirty()
|
|
{
|
|
return (m_actionList.count() != 0) || m_bDirty;
|
|
}
|