2
* Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
4
* This program is free software; you can redistribute it and/or modify
5
* it under the terms of the GNU General Public License version 2 as
6
* published by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful,
9
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
* GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License
14
* along with this program; if not, write to the Free Software
15
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
#include <QTextStream>
27
#include <kstandarddirs.h>
32
#define MF_MENU "Menu"
33
#define MF_PUBLIC_ID "-//freedesktop//DTD Menu 1.0//EN"
34
#define MF_SYSTEM_ID "http://www.freedesktop.org/standards/menu-spec/1.0/menu.dtd"
35
#define MF_NAME "Name"
36
#define MF_INCLUDE "Include"
37
#define MF_EXCLUDE "Exclude"
38
#define MF_FILENAME "Filename"
39
#define MF_DELETED "Deleted"
40
#define MF_NOTDELETED "NotDeleted"
41
#define MF_MOVE "Move"
44
#define MF_DIRECTORY "Directory"
45
#define MF_LAYOUT "Layout"
46
#define MF_MENUNAME "Menuname"
47
#define MF_SEPARATOR "Separator"
48
#define MF_MERGE "Merge"
50
MenuFile::MenuFile(const QString &file)
51
: m_fileName(file), m_bDirty(false)
62
if (m_fileName.isEmpty())
65
QFile file( m_fileName );
66
if (!file.open( QIODevice::ReadOnly ))
69
kWarning() << "Could not read " << m_fileName ;
77
if ( !m_doc.setContent( &file, &errorMsg, &errorRow, &errorCol ) ) {
78
kWarning() << "Parse error in " << m_fileName << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg ;
88
void MenuFile::create()
90
QDomImplementation impl;
91
QDomDocumentType docType = impl.createDocumentType( MF_MENU, MF_PUBLIC_ID, MF_SYSTEM_ID );
92
m_doc = impl.createDocument(QString(), MF_MENU, docType);
97
QFile file( m_fileName );
99
if (!file.open( QIODevice::WriteOnly ))
101
kWarning() << "Could not write " << m_fileName ;
102
m_error = i18n("Could not write to %1", m_fileName);
105
QTextStream stream( &file );
106
stream.setCodec( "UTF-8" );
108
stream << m_doc.toString();
112
if (file.error() != QFile::NoError)
114
kWarning() << "Could not close " << m_fileName ;
115
m_error = i18n("Could not write to %1", m_fileName);
124
QDomElement MenuFile::findMenu(QDomElement elem, const QString &menuName, bool create)
126
QString menuNodeName;
128
int i = menuName.indexOf('/');
131
menuNodeName = menuName.left(i);
132
subMenuName = menuName.mid(i+1);
136
menuNodeName = menuName;
139
return findMenu(elem, subMenuName, create);
141
if (menuNodeName.isEmpty())
144
QDomNode n = elem.firstChild();
147
QDomElement e = n.toElement(); // try to convert the node to an element.
148
if (e.tagName() == MF_MENU)
152
QDomNode n2 = e.firstChild();
153
while ( !n2.isNull() )
155
QDomElement e2 = n2.toElement();
156
if (!e2.isNull() && e2.tagName() == MF_NAME)
161
n2 = n2.nextSibling();
164
if (name == menuNodeName)
166
if (subMenuName.isEmpty())
169
return findMenu(e, subMenuName, create);
176
return QDomElement();
179
QDomElement newElem = m_doc.createElement(MF_MENU);
180
QDomElement newNameElem = m_doc.createElement(MF_NAME);
181
newNameElem.appendChild(m_doc.createTextNode(menuNodeName));
182
newElem.appendChild(newNameElem);
183
elem.appendChild(newElem);
185
if (subMenuName.isEmpty())
188
return findMenu(newElem, subMenuName, create);
191
static QString entryToDirId(const QString &path)
193
// See also KDesktopFile::locateLocal
195
if (QFileInfo(path).isAbsolute())
197
// XDG Desktop menu items come with absolute paths, we need to
198
// extract their relative path and then build a local path.
199
local = KGlobal::dirs()->relativeLocation("xdgdata-dirs", path);
202
if (local.isEmpty() || local.startsWith('/'))
204
// What now? Use filename only and hope for the best.
205
local = path.mid(path.lastIndexOf('/')+1);
210
static void purgeIncludesExcludes(QDomElement elem, const QString &appId, QDomElement &excludeNode, QDomElement &includeNode)
212
// Remove any previous includes/excludes of appId
213
QDomNode n = elem.firstChild();
216
QDomElement e = n.toElement(); // try to convert the node to an element.
217
bool bIncludeNode = (e.tagName() == MF_INCLUDE);
218
bool bExcludeNode = (e.tagName() == MF_EXCLUDE);
223
if (bIncludeNode || bExcludeNode)
225
QDomNode n2 = e.firstChild();
226
while ( !n2.isNull() )
228
QDomNode next = n2.nextSibling();
229
QDomElement e2 = n2.toElement();
230
if (!e2.isNull() && e2.tagName() == MF_FILENAME)
232
if (e2.text() == appId)
245
static void purgeDeleted(QDomElement elem)
247
// Remove any previous includes/excludes of appId
248
QDomNode n = elem.firstChild();
251
QDomNode next = n.nextSibling();
252
QDomElement e = n.toElement(); // try to convert the node to an element.
253
if ((e.tagName() == MF_DELETED) ||
254
(e.tagName() == MF_NOTDELETED))
262
static void purgeLayout(QDomElement elem)
264
// Remove any previous includes/excludes of appId
265
QDomNode n = elem.firstChild();
268
QDomNode next = n.nextSibling();
269
QDomElement e = n.toElement(); // try to convert the node to an element.
270
if (e.tagName() == MF_LAYOUT)
278
void MenuFile::addEntry(const QString &menuName, const QString &menuId)
282
m_removedEntries.removeAll(menuId);
284
QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
286
QDomElement excludeNode;
287
QDomElement includeNode;
289
purgeIncludesExcludes(elem, menuId, excludeNode, includeNode);
291
if (includeNode.isNull())
293
includeNode = m_doc.createElement(MF_INCLUDE);
294
elem.appendChild(includeNode);
297
QDomElement fileNode = m_doc.createElement(MF_FILENAME);
298
fileNode.appendChild(m_doc.createTextNode(menuId));
299
includeNode.appendChild(fileNode);
302
void MenuFile::setLayout(const QString &menuName, const QStringList &layout)
306
QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
310
QDomElement layoutNode = m_doc.createElement(MF_LAYOUT);
311
elem.appendChild(layoutNode);
313
for(QStringList::ConstIterator it = layout.constBegin();
314
it != layout.constEnd(); ++it)
319
layoutNode.appendChild(m_doc.createElement(MF_SEPARATOR));
323
QDomElement mergeNode = m_doc.createElement(MF_MERGE);
324
mergeNode.setAttribute("type", "menus");
325
layoutNode.appendChild(mergeNode);
329
QDomElement mergeNode = m_doc.createElement(MF_MERGE);
330
mergeNode.setAttribute("type", "files");
331
layoutNode.appendChild(mergeNode);
335
QDomElement mergeNode = m_doc.createElement(MF_MERGE);
336
mergeNode.setAttribute("type", "all");
337
layoutNode.appendChild(mergeNode);
339
else if (li.endsWith('/'))
341
li.truncate(li.length()-1);
342
QDomElement menuNode = m_doc.createElement(MF_MENUNAME);
343
menuNode.appendChild(m_doc.createTextNode(li));
344
layoutNode.appendChild(menuNode);
348
QDomElement fileNode = m_doc.createElement(MF_FILENAME);
349
fileNode.appendChild(m_doc.createTextNode(li));
350
layoutNode.appendChild(fileNode);
356
void MenuFile::removeEntry(const QString &menuName, const QString &menuId)
359
m_removedEntries.append(menuId);
361
QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
363
QDomElement excludeNode;
364
QDomElement includeNode;
366
purgeIncludesExcludes(elem, menuId, excludeNode, includeNode);
368
if (excludeNode.isNull())
370
excludeNode = m_doc.createElement(MF_EXCLUDE);
371
elem.appendChild(excludeNode);
373
QDomElement fileNode = m_doc.createElement(MF_FILENAME);
374
fileNode.appendChild(m_doc.createTextNode(menuId));
375
excludeNode.appendChild(fileNode);
378
void MenuFile::addMenu(const QString &menuName, const QString &menuFile)
381
QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
383
QDomElement dirElem = m_doc.createElement(MF_DIRECTORY);
384
dirElem.appendChild(m_doc.createTextNode(entryToDirId(menuFile)));
385
elem.appendChild(dirElem);
388
void MenuFile::moveMenu(const QString &oldMenu, const QString &newMenu)
392
// Undelete the new menu
393
QDomElement elem = findMenu(m_doc.documentElement(), newMenu, true);
395
elem.appendChild(m_doc.createElement(MF_NOTDELETED));
397
// TODO: GET RID OF COMMON PART, IT BREAKS STUFF
399
QStringList oldMenuParts = oldMenu.split( '/');
400
QStringList newMenuParts = newMenu.split( '/');
401
QString commonMenuName;
402
int max = qMin(oldMenuParts.count(), newMenuParts.count());
406
if (oldMenuParts[i] != newMenuParts[i])
408
commonMenuName += '/' + oldMenuParts[i];
411
for(int j = i; j < oldMenuParts.count()-1; j++)
415
oldMenuName += oldMenuParts[j];
418
for(int j = i; j < newMenuParts.count()-1; j++)
422
newMenuName += newMenuParts[j];
425
if (oldMenuName == newMenuName) return; // Can happen
427
elem = findMenu(m_doc.documentElement(), commonMenuName, true);
429
// Add instructions for moving
430
QDomElement moveNode = m_doc.createElement(MF_MOVE);
431
QDomElement node = m_doc.createElement(MF_OLD);
432
node.appendChild(m_doc.createTextNode(oldMenuName));
433
moveNode.appendChild(node);
434
node = m_doc.createElement(MF_NEW);
435
node.appendChild(m_doc.createTextNode(newMenuName));
436
moveNode.appendChild(node);
437
elem.appendChild(moveNode);
440
void MenuFile::removeMenu(const QString &menuName)
444
QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
447
elem.appendChild(m_doc.createElement(MF_DELETED));
451
* Returns a unique menu-name for a new menu under @p menuName
452
* inspired by @p newMenu
454
QString MenuFile::uniqueMenuName(const QString &menuName, const QString &newMenu, const QStringList & excludeList)
456
QDomElement elem = findMenu(m_doc.documentElement(), menuName, false);
458
QString result = newMenu;
459
if (result.endsWith('/'))
460
result.truncate(result.length()-1);
462
QRegExp r("(.*)(?=-\\d+)");
463
result = (r.indexIn(result) > -1) ? r.cap(1) : result;
465
int trunc = result.length(); // Position of trailing '/'
469
for(int n = 1; ++n; )
471
if (findMenu(elem, result, false).isNull() && !excludeList.contains(result))
474
result.truncate(trunc);
475
result.append(QString("-%1/").arg(n));
477
return QString(); // Never reached
480
void MenuFile::performAction(const ActionAtom *atom)
485
addEntry(atom->arg1, atom->arg2);
488
removeEntry(atom->arg1, atom->arg2);
491
addMenu(atom->arg1, atom->arg2);
494
removeMenu(atom->arg1);
497
moveMenu(atom->arg1, atom->arg2);
502
MenuFile::ActionAtom *MenuFile::pushAction(MenuFile::ActionType action, const QString &arg1, const QString &arg2)
504
ActionAtom *atom = new ActionAtom;
505
atom->action = action;
508
m_actionList.append(atom);
512
void MenuFile::popAction(ActionAtom *atom)
514
if (m_actionList.last() != atom)
516
qWarning("MenuFile::popAction Error, action not last in list.");
519
m_actionList.removeLast();
523
bool MenuFile::performAllActions()
525
Q_FOREACH(ActionAtom *atom, m_actionList) {
526
performAction( atom );
529
m_actionList.clear();
531
// Entries that have been removed from the menu are added to .hidden
532
// so that they don't re-appear in Lost & Found
533
QStringList removed = m_removedEntries;
534
m_removedEntries.clear();
535
for(QStringList::ConstIterator it = removed.constBegin();
536
it != removed.constEnd(); ++it)
538
addEntry("/.hidden/", *it);
541
m_removedEntries.clear();
549
bool MenuFile::dirty()
551
return (m_actionList.count() != 0) || m_bDirty;
554
void MenuFile::restoreMenuSystem( const QString &filename)
558
m_fileName = filename;
561
Q_FOREACH(ActionAtom *atom, m_actionList) {
564
m_actionList.clear();
566
m_removedEntries.clear();