~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to kmenuedit/menufile.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *   Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
 
3
 *
 
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.
 
7
 *
 
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.
 
12
 *
 
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.
 
16
 *
 
17
 */
 
18
 
 
19
#include <QFile>
 
20
#include <QTextStream>
 
21
#include <QRegExp>
 
22
#include <QFileInfo>
 
23
 
 
24
#include <kdebug.h>
 
25
#include <kglobal.h>
 
26
#include <klocale.h>
 
27
#include <kstandarddirs.h>
 
28
 
 
29
#include "menufile.h"
 
30
 
 
31
 
 
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"
 
42
#define MF_OLD          "Old"
 
43
#define MF_NEW          "New"
 
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"
 
49
 
 
50
MenuFile::MenuFile(const QString &file)
 
51
 : m_fileName(file), m_bDirty(false)
 
52
{
 
53
   load();
 
54
}
 
55
 
 
56
MenuFile::~MenuFile()
 
57
{
 
58
}
 
59
 
 
60
bool MenuFile::load()
 
61
{
 
62
   if (m_fileName.isEmpty())
 
63
      return false;
 
64
 
 
65
   QFile file( m_fileName );
 
66
   if (!file.open( QIODevice::ReadOnly ))
 
67
   {
 
68
       if ( file.exists() )
 
69
           kWarning() << "Could not read " << m_fileName ;
 
70
       create();
 
71
       return false;
 
72
   }
 
73
 
 
74
   QString errorMsg;
 
75
   int errorRow;
 
76
   int errorCol;
 
77
   if ( !m_doc.setContent( &file, &errorMsg, &errorRow, &errorCol ) ) {
 
78
      kWarning() << "Parse error in " << m_fileName << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg ;
 
79
      file.close();
 
80
      create();
 
81
      return false;
 
82
   }
 
83
   file.close();
 
84
 
 
85
   return true;
 
86
}
 
87
 
 
88
void MenuFile::create()
 
89
{
 
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);
 
93
}
 
94
 
 
95
bool MenuFile::save()
 
96
{
 
97
   QFile file( m_fileName );
 
98
 
 
99
   if (!file.open( QIODevice::WriteOnly ))
 
100
   {
 
101
      kWarning() << "Could not write " << m_fileName ;
 
102
      m_error = i18n("Could not write to %1", m_fileName);
 
103
      return false;
 
104
   }
 
105
   QTextStream stream( &file );
 
106
   stream.setCodec( "UTF-8" );
 
107
 
 
108
   stream << m_doc.toString();
 
109
 
 
110
   file.close();
 
111
 
 
112
   if (file.error() != QFile::NoError)
 
113
   {
 
114
      kWarning() << "Could not close " << m_fileName ;
 
115
      m_error = i18n("Could not write to %1", m_fileName);
 
116
      return false;
 
117
   }
 
118
 
 
119
   m_bDirty = false;
 
120
 
 
121
   return true;
 
122
}
 
123
 
 
124
QDomElement MenuFile::findMenu(QDomElement elem, const QString &menuName, bool create)
 
125
{
 
126
   QString menuNodeName;
 
127
   QString subMenuName;
 
128
   int i = menuName.indexOf('/');
 
129
   if (i >= 0)
 
130
   {
 
131
      menuNodeName = menuName.left(i);
 
132
      subMenuName = menuName.mid(i+1);
 
133
   }
 
134
   else
 
135
   {
 
136
      menuNodeName = menuName;
 
137
   }
 
138
   if (i == 0)
 
139
      return findMenu(elem, subMenuName, create);
 
140
 
 
141
   if (menuNodeName.isEmpty())
 
142
      return elem;
 
143
 
 
144
   QDomNode n = elem.firstChild();
 
145
   while( !n.isNull() )
 
146
   {
 
147
      QDomElement e = n.toElement(); // try to convert the node to an element.
 
148
      if (e.tagName() == MF_MENU)
 
149
      {
 
150
         QString name;
 
151
 
 
152
         QDomNode n2 = e.firstChild();
 
153
         while ( !n2.isNull() )
 
154
         {
 
155
            QDomElement e2 = n2.toElement();
 
156
            if (!e2.isNull() && e2.tagName() == MF_NAME)
 
157
            {
 
158
               name = e2.text();
 
159
               break;
 
160
            }
 
161
            n2 = n2.nextSibling();
 
162
         }
 
163
 
 
164
         if (name == menuNodeName)
 
165
         {
 
166
            if (subMenuName.isEmpty())
 
167
               return e;
 
168
            else
 
169
               return findMenu(e, subMenuName, create);
 
170
         }
 
171
      }
 
172
      n = n.nextSibling();
 
173
   }
 
174
 
 
175
   if (!create)
 
176
      return QDomElement();
 
177
 
 
178
   // Create new node.
 
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);
 
184
 
 
185
   if (subMenuName.isEmpty())
 
186
      return newElem;
 
187
   else
 
188
      return findMenu(newElem, subMenuName, create);
 
189
}
 
190
 
 
191
static QString entryToDirId(const QString &path)
 
192
{
 
193
   // See also KDesktopFile::locateLocal
 
194
   QString local;
 
195
   if (QFileInfo(path).isAbsolute())
 
196
   {
 
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);
 
200
   }
 
201
 
 
202
   if (local.isEmpty() || local.startsWith('/'))
 
203
   {
 
204
      // What now? Use filename only and hope for the best.
 
205
      local = path.mid(path.lastIndexOf('/')+1);
 
206
   }
 
207
   return local;
 
208
}
 
209
 
 
210
static void purgeIncludesExcludes(QDomElement elem, const QString &appId, QDomElement &excludeNode, QDomElement &includeNode)
 
211
{
 
212
   // Remove any previous includes/excludes of appId
 
213
   QDomNode n = elem.firstChild();
 
214
   while( !n.isNull() )
 
215
   {
 
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);
 
219
      if (bIncludeNode)
 
220
         includeNode = e;
 
221
      if (bExcludeNode)
 
222
         excludeNode = e;
 
223
      if (bIncludeNode || bExcludeNode)
 
224
      {
 
225
         QDomNode n2 = e.firstChild();
 
226
         while ( !n2.isNull() )
 
227
         {
 
228
            QDomNode next = n2.nextSibling();
 
229
            QDomElement e2 = n2.toElement();
 
230
            if (!e2.isNull() && e2.tagName() == MF_FILENAME)
 
231
            {
 
232
               if (e2.text() == appId)
 
233
               {
 
234
                  e.removeChild(e2);
 
235
                  break;
 
236
               }
 
237
            }
 
238
            n2 = next;
 
239
         }
 
240
      }
 
241
      n = n.nextSibling();
 
242
   }
 
243
}
 
244
 
 
245
static void purgeDeleted(QDomElement elem)
 
246
{
 
247
   // Remove any previous includes/excludes of appId
 
248
   QDomNode n = elem.firstChild();
 
249
   while( !n.isNull() )
 
250
   {
 
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))
 
255
      {
 
256
         elem.removeChild(e);
 
257
      }
 
258
      n = next;
 
259
   }
 
260
}
 
261
 
 
262
static void purgeLayout(QDomElement elem)
 
263
{
 
264
   // Remove any previous includes/excludes of appId
 
265
   QDomNode n = elem.firstChild();
 
266
   while( !n.isNull() )
 
267
   {
 
268
      QDomNode next = n.nextSibling();
 
269
      QDomElement e = n.toElement(); // try to convert the node to an element.
 
270
      if (e.tagName() == MF_LAYOUT)
 
271
      {
 
272
         elem.removeChild(e);
 
273
      }
 
274
      n = next;
 
275
   }
 
276
}
 
277
 
 
278
void MenuFile::addEntry(const QString &menuName, const QString &menuId)
 
279
{
 
280
   m_bDirty = true;
 
281
 
 
282
   m_removedEntries.removeAll(menuId);
 
283
 
 
284
   QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
 
285
 
 
286
   QDomElement excludeNode;
 
287
   QDomElement includeNode;
 
288
 
 
289
   purgeIncludesExcludes(elem, menuId, excludeNode, includeNode);
 
290
 
 
291
   if (includeNode.isNull())
 
292
   {
 
293
      includeNode = m_doc.createElement(MF_INCLUDE);
 
294
      elem.appendChild(includeNode);
 
295
   }
 
296
 
 
297
   QDomElement fileNode = m_doc.createElement(MF_FILENAME);
 
298
   fileNode.appendChild(m_doc.createTextNode(menuId));
 
299
   includeNode.appendChild(fileNode);
 
300
}
 
301
 
 
302
void MenuFile::setLayout(const QString &menuName, const QStringList &layout)
 
303
{
 
304
   m_bDirty = true;
 
305
 
 
306
   QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
 
307
 
 
308
   purgeLayout(elem);
 
309
 
 
310
   QDomElement layoutNode = m_doc.createElement(MF_LAYOUT);
 
311
   elem.appendChild(layoutNode);
 
312
 
 
313
   for(QStringList::ConstIterator it = layout.constBegin();
 
314
       it != layout.constEnd(); ++it)
 
315
   {
 
316
      QString li = *it;
 
317
      if (li == ":S")
 
318
      {
 
319
         layoutNode.appendChild(m_doc.createElement(MF_SEPARATOR));
 
320
      }
 
321
      else if (li == ":M")
 
322
      {
 
323
         QDomElement mergeNode = m_doc.createElement(MF_MERGE);
 
324
         mergeNode.setAttribute("type", "menus");
 
325
         layoutNode.appendChild(mergeNode);
 
326
      }
 
327
      else if (li == ":F")
 
328
      {
 
329
         QDomElement mergeNode = m_doc.createElement(MF_MERGE);
 
330
         mergeNode.setAttribute("type", "files");
 
331
         layoutNode.appendChild(mergeNode);
 
332
      }
 
333
      else if (li == ":A")
 
334
      {
 
335
         QDomElement mergeNode = m_doc.createElement(MF_MERGE);
 
336
         mergeNode.setAttribute("type", "all");
 
337
         layoutNode.appendChild(mergeNode);
 
338
      }
 
339
      else if (li.endsWith('/'))
 
340
      {
 
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);
 
345
      }
 
346
      else
 
347
      {
 
348
         QDomElement fileNode = m_doc.createElement(MF_FILENAME);
 
349
         fileNode.appendChild(m_doc.createTextNode(li));
 
350
         layoutNode.appendChild(fileNode);
 
351
      }
 
352
   }
 
353
}
 
354
 
 
355
 
 
356
void MenuFile::removeEntry(const QString &menuName, const QString &menuId)
 
357
{
 
358
   m_bDirty = true;
 
359
   m_removedEntries.append(menuId);
 
360
 
 
361
   QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
 
362
 
 
363
   QDomElement excludeNode;
 
364
   QDomElement includeNode;
 
365
 
 
366
   purgeIncludesExcludes(elem, menuId, excludeNode, includeNode);
 
367
 
 
368
   if (excludeNode.isNull())
 
369
   {
 
370
      excludeNode = m_doc.createElement(MF_EXCLUDE);
 
371
      elem.appendChild(excludeNode);
 
372
   }
 
373
   QDomElement fileNode = m_doc.createElement(MF_FILENAME);
 
374
   fileNode.appendChild(m_doc.createTextNode(menuId));
 
375
   excludeNode.appendChild(fileNode);
 
376
}
 
377
 
 
378
void MenuFile::addMenu(const QString &menuName, const QString &menuFile)
 
379
{
 
380
   m_bDirty = true;
 
381
   QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
 
382
 
 
383
   QDomElement dirElem = m_doc.createElement(MF_DIRECTORY);
 
384
   dirElem.appendChild(m_doc.createTextNode(entryToDirId(menuFile)));
 
385
   elem.appendChild(dirElem);
 
386
}
 
387
 
 
388
void MenuFile::moveMenu(const QString &oldMenu, const QString &newMenu)
 
389
{
 
390
   m_bDirty = true;
 
391
 
 
392
   // Undelete the new menu
 
393
   QDomElement elem = findMenu(m_doc.documentElement(), newMenu, true);
 
394
   purgeDeleted(elem);
 
395
   elem.appendChild(m_doc.createElement(MF_NOTDELETED));
 
396
 
 
397
// TODO: GET RID OF COMMON PART, IT BREAKS STUFF
 
398
   // Find common part
 
399
   QStringList oldMenuParts = oldMenu.split( '/');
 
400
   QStringList newMenuParts = newMenu.split( '/');
 
401
   QString commonMenuName;
 
402
   int max = qMin(oldMenuParts.count(), newMenuParts.count());
 
403
   int i = 0;
 
404
   for(; i < max; i++)
 
405
   {
 
406
      if (oldMenuParts[i] != newMenuParts[i])
 
407
         break;
 
408
      commonMenuName += '/' + oldMenuParts[i];
 
409
   }
 
410
   QString oldMenuName;
 
411
   for(int j = i; j < oldMenuParts.count()-1; j++)
 
412
   {
 
413
      if (i != j)
 
414
         oldMenuName += '/';
 
415
      oldMenuName += oldMenuParts[j];
 
416
   }
 
417
   QString newMenuName;
 
418
   for(int j = i; j < newMenuParts.count()-1; j++)
 
419
   {
 
420
      if (i != j)
 
421
         newMenuName += '/';
 
422
      newMenuName += newMenuParts[j];
 
423
   }
 
424
 
 
425
   if (oldMenuName == newMenuName) return; // Can happen
 
426
 
 
427
   elem = findMenu(m_doc.documentElement(), commonMenuName, true);
 
428
 
 
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);
 
438
}
 
439
 
 
440
void MenuFile::removeMenu(const QString &menuName)
 
441
{
 
442
   m_bDirty = true;
 
443
 
 
444
   QDomElement elem = findMenu(m_doc.documentElement(), menuName, true);
 
445
 
 
446
   purgeDeleted(elem);
 
447
   elem.appendChild(m_doc.createElement(MF_DELETED));
 
448
}
 
449
 
 
450
   /**
 
451
    * Returns a unique menu-name for a new menu under @p menuName
 
452
    * inspired by @p newMenu
 
453
    */
 
454
QString MenuFile::uniqueMenuName(const QString &menuName, const QString &newMenu, const QStringList & excludeList)
 
455
{
 
456
   QDomElement elem = findMenu(m_doc.documentElement(), menuName, false);
 
457
 
 
458
   QString result = newMenu;
 
459
   if (result.endsWith('/'))
 
460
       result.truncate(result.length()-1);
 
461
 
 
462
   QRegExp r("(.*)(?=-\\d+)");
 
463
   result = (r.indexIn(result) > -1) ? r.cap(1) : result;
 
464
 
 
465
   int trunc = result.length(); // Position of trailing '/'
 
466
 
 
467
   result.append("/");
 
468
 
 
469
   for(int n = 1; ++n; )
 
470
   {
 
471
      if (findMenu(elem, result, false).isNull() && !excludeList.contains(result))
 
472
         return result;
 
473
 
 
474
      result.truncate(trunc);
 
475
      result.append(QString("-%1/").arg(n));
 
476
   }
 
477
   return QString(); // Never reached
 
478
}
 
479
 
 
480
void MenuFile::performAction(const ActionAtom *atom)
 
481
{
 
482
   switch(atom->action)
 
483
   {
 
484
     case ADD_ENTRY:
 
485
        addEntry(atom->arg1, atom->arg2);
 
486
        return;
 
487
     case REMOVE_ENTRY:
 
488
        removeEntry(atom->arg1, atom->arg2);
 
489
        return;
 
490
     case ADD_MENU:
 
491
        addMenu(atom->arg1, atom->arg2);
 
492
        return;
 
493
     case REMOVE_MENU:
 
494
        removeMenu(atom->arg1);
 
495
        return;
 
496
     case MOVE_MENU:
 
497
        moveMenu(atom->arg1, atom->arg2);
 
498
        return;
 
499
   }
 
500
}
 
501
 
 
502
MenuFile::ActionAtom *MenuFile::pushAction(MenuFile::ActionType action, const QString &arg1, const QString &arg2)
 
503
{
 
504
   ActionAtom *atom = new ActionAtom;
 
505
   atom->action = action;
 
506
   atom->arg1 = arg1;
 
507
   atom->arg2 = arg2;
 
508
   m_actionList.append(atom);
 
509
   return atom;
 
510
}
 
511
 
 
512
void MenuFile::popAction(ActionAtom *atom)
 
513
{
 
514
   if (m_actionList.last() != atom)
 
515
   {
 
516
      qWarning("MenuFile::popAction Error, action not last in list.");
 
517
      return;
 
518
   }
 
519
   m_actionList.removeLast();
 
520
   delete atom;
 
521
}
 
522
 
 
523
bool MenuFile::performAllActions()
 
524
{
 
525
    Q_FOREACH(ActionAtom *atom, m_actionList) {
 
526
        performAction( atom );
 
527
        delete atom;
 
528
    }
 
529
    m_actionList.clear();
 
530
 
 
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)
 
537
   {
 
538
      addEntry("/.hidden/", *it);
 
539
   }
 
540
 
 
541
   m_removedEntries.clear();
 
542
 
 
543
   if (!m_bDirty)
 
544
      return true;
 
545
 
 
546
   return save();
 
547
}
 
548
 
 
549
bool MenuFile::dirty()
 
550
{
 
551
   return (m_actionList.count() != 0) || m_bDirty;
 
552
}
 
553
 
 
554
void MenuFile::restoreMenuSystem( const QString &filename)
 
555
{
 
556
    m_error.clear();
 
557
 
 
558
    m_fileName = filename;
 
559
    m_doc.clear();
 
560
    m_bDirty = false;
 
561
    Q_FOREACH(ActionAtom *atom, m_actionList) {
 
562
        delete atom;
 
563
    }
 
564
    m_actionList.clear();
 
565
 
 
566
    m_removedEntries.clear();
 
567
    create();
 
568
}