summaryrefslogtreecommitdiffstats
path: root/kmenuedit/menufile.cpp
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commit4aed2c8219774f5d797760606b8489a92ddc5163 (patch)
tree3f8c130f7d269626bf6a9447407ef6c35954426a /kmenuedit/menufile.cpp
downloadtdebase-4aed2c8219774f5d797760606b8489a92ddc5163.tar.gz
tdebase-4aed2c8219774f5d797760606b8489a92ddc5163.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdebase@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kmenuedit/menufile.cpp')
-rw-r--r--kmenuedit/menufile.cpp552
1 files changed, 552 insertions, 0 deletions
diff --git a/kmenuedit/menufile.cpp b/kmenuedit/menufile.cpp
new file mode 100644
index 000000000..7fe2be954
--- /dev/null
+++ b/kmenuedit/menufile.cpp
@@ -0,0 +1,552 @@
+/*
+ * 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 <qfile.h>
+#include <qtextstream.h>
+#include <qregexp.h>
+
+#include <kdebug.h>
+#include <kglobal.h>
+#include <klocale.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 QString &file)
+ : m_fileName(file), m_bDirty(false)
+{
+ load();
+}
+
+MenuFile::~MenuFile()
+{
+}
+
+bool MenuFile::load()
+{
+ if (m_fileName.isEmpty())
+ return false;
+
+ QFile file( m_fileName );
+ if (!file.open( IO_ReadOnly ))
+ {
+ kdWarning() << "Could not read " << m_fileName << endl;
+ create();
+ return false;
+ }
+
+ QString 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()
+{
+ QDomImplementation impl;
+ QDomDocumentType docType = impl.createDocumentType( MF_MENU, MF_PUBLIC_ID, MF_SYSTEM_ID );
+ m_doc = impl.createDocument(QString::null, MF_MENU, docType);
+}
+
+bool MenuFile::save()
+{
+ QFile 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;
+ }
+ QTextStream stream( &file );
+ stream.setEncoding(QTextStream::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;
+}
+
+QDomElement MenuFile::findMenu(QDomElement elem, const QString &menuName, bool create)
+{
+ QString menuNodeName;
+ QString 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;
+
+ QDomNode n = elem.firstChild();
+ while( !n.isNull() )
+ {
+ QDomElement e = n.toElement(); // try to convert the node to an element.
+ if (e.tagName() == MF_MENU)
+ {
+ QString name;
+
+ QDomNode n2 = e.firstChild();
+ while ( !n2.isNull() )
+ {
+ QDomElement 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 QDomElement();
+
+ // Create new node.
+ QDomElement newElem = m_doc.createElement(MF_MENU);
+ QDomElement 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 QString entryToDirId(const QString &path)
+{
+ // See also KDesktopFile::locateLocal
+ QString 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 = KGlobal::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(QDomElement elem, const QString &appId, QDomElement &excludeNode, QDomElement &includeNode)
+{
+ // Remove any previous includes/excludes of appId
+ QDomNode n = elem.firstChild();
+ while( !n.isNull() )
+ {
+ QDomElement 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)
+ {
+ QDomNode n2 = e.firstChild();
+ while ( !n2.isNull() )
+ {
+ QDomNode next = n2.nextSibling();
+ QDomElement 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(QDomElement elem)
+{
+ // Remove any previous includes/excludes of appId
+ QDomNode n = elem.firstChild();
+ while( !n.isNull() )
+ {
+ QDomNode next = n.nextSibling();
+ QDomElement 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(QDomElement elem)
+{
+ // Remove any previous includes/excludes of appId
+ QDomNode n = elem.firstChild();
+ while( !n.isNull() )
+ {
+ QDomNode next = n.nextSibling();
+ QDomElement 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 QString &menuName, const QString &menuId)
+{
+ m_bDirty = true;
+
+ m_removedEntries.remove(menuId);
+
+ QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
+
+ QDomElement excludeNode;
+ QDomElement includeNode;
+
+ purgeIncludesExcludes(elem, menuId, excludeNode, includeNode);
+
+ if (includeNode.isNull())
+ {
+ includeNode = m_doc.createElement(MF_INCLUDE);
+ elem.appendChild(includeNode);
+ }
+
+ QDomElement fileNode = m_doc.createElement(MF_FILENAME);
+ fileNode.appendChild(m_doc.createTextNode(menuId));
+ includeNode.appendChild(fileNode);
+}
+
+void MenuFile::setLayout(const QString &menuName, const QStringList &layout)
+{
+ m_bDirty = true;
+
+ QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
+
+ purgeLayout(elem);
+
+ QDomElement layoutNode = m_doc.createElement(MF_LAYOUT);
+ elem.appendChild(layoutNode);
+
+ for(QStringList::ConstIterator it = layout.begin();
+ it != layout.end(); ++it)
+ {
+ QString li = *it;
+ if (li == ":S")
+ {
+ layoutNode.appendChild(m_doc.createElement(MF_SEPARATOR));
+ }
+ else if (li == ":M")
+ {
+ QDomElement mergeNode = m_doc.createElement(MF_MERGE);
+ mergeNode.setAttribute("type", "menus");
+ layoutNode.appendChild(mergeNode);
+ }
+ else if (li == ":F")
+ {
+ QDomElement mergeNode = m_doc.createElement(MF_MERGE);
+ mergeNode.setAttribute("type", "files");
+ layoutNode.appendChild(mergeNode);
+ }
+ else if (li == ":A")
+ {
+ QDomElement mergeNode = m_doc.createElement(MF_MERGE);
+ mergeNode.setAttribute("type", "all");
+ layoutNode.appendChild(mergeNode);
+ }
+ else if (li.endsWith("/"))
+ {
+ li.truncate(li.length()-1);
+ QDomElement menuNode = m_doc.createElement(MF_MENUNAME);
+ menuNode.appendChild(m_doc.createTextNode(li));
+ layoutNode.appendChild(menuNode);
+ }
+ else
+ {
+ QDomElement fileNode = m_doc.createElement(MF_FILENAME);
+ fileNode.appendChild(m_doc.createTextNode(li));
+ layoutNode.appendChild(fileNode);
+ }
+ }
+}
+
+
+void MenuFile::removeEntry(const QString &menuName, const QString &menuId)
+{
+ m_bDirty = true;
+
+ m_removedEntries.append(menuId);
+
+ QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
+
+ QDomElement excludeNode;
+ QDomElement includeNode;
+
+ purgeIncludesExcludes(elem, menuId, excludeNode, includeNode);
+
+ if (excludeNode.isNull())
+ {
+ excludeNode = m_doc.createElement(MF_EXCLUDE);
+ elem.appendChild(excludeNode);
+ }
+
+ QDomElement fileNode = m_doc.createElement(MF_FILENAME);
+ fileNode.appendChild(m_doc.createTextNode(menuId));
+ excludeNode.appendChild(fileNode);
+}
+
+void MenuFile::addMenu(const QString &menuName, const QString &menuFile)
+{
+ m_bDirty = true;
+ QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
+
+ QDomElement dirElem = m_doc.createElement(MF_DIRECTORY);
+ dirElem.appendChild(m_doc.createTextNode(entryToDirId(menuFile)));
+ elem.appendChild(dirElem);
+}
+
+void MenuFile::moveMenu(const QString &oldMenu, const QString &newMenu)
+{
+ m_bDirty = true;
+
+ // Undelete the new menu
+ QDomElement 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
+ QStringList oldMenuParts = QStringList::split('/', oldMenu);
+ QStringList newMenuParts = QStringList::split('/', newMenu);
+ QString commonMenuName;
+ uint max = QMIN(oldMenuParts.count(), newMenuParts.count());
+ uint i = 0;
+ for(; i < max; i++)
+ {
+ if (oldMenuParts[i] != newMenuParts[i])
+ break;
+ commonMenuName += '/' + oldMenuParts[i];
+ }
+ QString oldMenuName;
+ for(uint j = i; j < oldMenuParts.count(); j++)
+ {
+ if (i != j)
+ oldMenuName += '/';
+ oldMenuName += oldMenuParts[j];
+ }
+ QString 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
+ QDomElement moveNode = m_doc.createElement(MF_MOVE);
+ QDomElement 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 QString &menuName)
+{
+ m_bDirty = true;
+
+ QDomElement 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
+ */
+QString MenuFile::uniqueMenuName(const QString &menuName, const QString &newMenu, const QStringList & excludeList)
+{
+ QDomElement elem = findMenu(m_doc.documentElement(), menuName, false);
+
+ QString result = newMenu;
+ if (result.endsWith("/"))
+ result.truncate(result.length()-1);
+
+ QRegExp 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(QString("-%1/").arg(n));
+ }
+ return QString::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 QString &arg1, const QString &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)
+ {
+ qWarning("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
+ QStringList removed = m_removedEntries;
+ m_removedEntries.clear();
+ for(QStringList::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;
+}