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 as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 *
18 */
19
20#include "menuinfo.h"
21
22#include <QRegExp>
23
24#include <KDesktopFile>
25#include <KStandardDirs>
26#include <KConfigGroup>
27
28#include "menufile.h"
29#ifndef Q_WS_WIN
30#include "khotkeys.h"
31#endif
32
33//
34// MenuFolderInfo
35//
36
37static QStringList *s_newShortcuts = 0;
38static QStringList *s_freeShortcuts = 0;
39static QStringList *s_deletedApps = 0;
40
41// Add separator
42void MenuFolderInfo::add(MenuSeparatorInfo *info, bool initial)
43{
44 if (initial)
45 initialLayout.append(info);
46}
47
48// Add sub menu
49void MenuFolderInfo::add(MenuFolderInfo *info, bool initial)
50{
51 subFolders.append(info);
52 if (initial)
53 initialLayout.append(info);
54}
55
56// Remove sub menu (without deleting it)
57void MenuFolderInfo::take(MenuFolderInfo *info)
58{
59 subFolders.removeAll(info);
60}
61
62// Remove sub menu (without deleting it)
63bool MenuFolderInfo::takeRecursive(MenuFolderInfo *info)
64{
65 if (subFolders.removeAll(info) > 0)
66 {
67 return true;
68 }
69
70 foreach (MenuFolderInfo *subFolderInfo, subFolders)
71 {
72 if (subFolderInfo->takeRecursive(info))
73 return true;
74 }
75 return false;
76}
77
78// Recursively update all fullIds
79void MenuFolderInfo::updateFullId(const QString &parentId)
80{
81 fullId = parentId + id;
82
83 foreach (MenuFolderInfo *subFolderInfo, subFolders)
84 {
85 subFolderInfo->updateFullId(fullId);
86 }
87}
88
89// Add entry
90void MenuFolderInfo::add(MenuEntryInfo *entry, bool initial)
91{
92 entries.append(entry);
93 if (initial)
94 initialLayout.append(entry);
95}
96
97// Remove entry
98void MenuFolderInfo::take(MenuEntryInfo *entry)
99{
100 entries.removeAll(entry);
101}
102
103
104// Return a unique sub-menu caption inspired by @p caption
105QString MenuFolderInfo::uniqueMenuCaption(const QString &caption)
106{
107 QRegExp r("(.*)(?=-\\d+)");
108 QString cap = (r.indexIn(caption) > -1) ? r.cap(1) : caption;
109
110 QString result = caption;
111
112 for(int n = 1; ++n; )
113 {
114 bool ok = true;
115 foreach (MenuFolderInfo *subFolderInfo, subFolders)
116 {
117 if (subFolderInfo->caption == result)
118 {
119 ok = false;
120 break;
121 }
122 }
123 if (ok)
124 return result;
125
126 result = cap + QString("-%1").arg(n);
127 }
128 return QString(); // Never reached
129}
130
131// Return a unique item caption inspired by @p caption
132QString MenuFolderInfo::uniqueItemCaption(const QString &caption, const QString &exclude)
133{
134 QRegExp r("(.*)(?=-\\d+)");
135 QString cap = (r.indexIn(caption) > -1) ? r.cap(1) : caption;
136
137 QString result = caption;
138
139 for(int n = 1; ++n; )
140 {
141 bool ok = true;
142 if (result == exclude)
143 ok = false;
144 foreach (MenuEntryInfo *entryInfo, entries)
145 {
146 if (entryInfo->caption == result)
147 {
148 ok = false;
149 break;
150 }
151 }
152 if (ok)
153 return result;
154
155 result = cap + QString("-%1").arg(n);
156 }
157 return QString(); // Never reached
158}
159
160// Return a list of existing submenu ids
161QStringList MenuFolderInfo::existingMenuIds()
162{
163 QStringList result;
164 foreach (MenuFolderInfo *subFolderInfo, subFolders)
165 {
166 result.append(subFolderInfo->id);
167 }
168 return result;
169}
170
171void MenuFolderInfo::setDirty()
172{
173 dirty = true;
174}
175
176void MenuFolderInfo::save(MenuFile *menuFile)
177{
178 if (s_deletedApps)
179 {
180#ifndef Q_WS_WIN
181 // Remove hotkeys for applications that have been deleted
182 for(QStringList::ConstIterator it = s_deletedApps->constBegin();
183 it != s_deletedApps->constEnd(); ++it)
184 {
185 // The shorcut is deleted if we set a empty sequence
186 KHotKeys::changeMenuEntryShortcut(*it, "");
187 }
188#endif
189 delete s_deletedApps;
190 s_deletedApps = 0;
191 }
192
193 if (dirty)
194 {
195 QString local = KDesktopFile::locateLocal(directoryFile);
196
197 KDesktopFile *df = 0;
198 if (directoryFile != local)
199 {
200 KDesktopFile orig("apps", directoryFile);
201 df = orig.copyTo(local);
202 }
203 else
204 {
205 df = new KDesktopFile("apps", directoryFile);
206 }
207
208 KConfigGroup dg( df->desktopGroup() );
209 dg.writeEntry("Name", caption);
210 dg.writeEntry("GenericName", genericname);
211 dg.writeEntry("Comment", comment);
212 dg.writeEntry("Icon", icon);
213 dg.sync();
214 delete df;
215 dirty = false;
216 }
217
218 // Save sub-menus
219 foreach (MenuFolderInfo *subFolderInfo, subFolders)
220 {
221 subFolderInfo->save(menuFile);
222 }
223
224 // Save entries
225 foreach (MenuEntryInfo *entryInfo, entries)
226 {
227 if (entryInfo->needInsertion())
228 menuFile->addEntry(fullId, entryInfo->menuId());
229 entryInfo->save();
230 }
231}
232
233bool MenuFolderInfo::hasDirt()
234{
235 if (dirty) return true;
236
237 // Check sub-menus
238 foreach (MenuFolderInfo *subFolderInfo, subFolders)
239 {
240 if (subFolderInfo->hasDirt()) return true;
241 }
242
243 // Check entries
244 foreach (MenuEntryInfo *entryInfo, entries)
245 {
246 if (entryInfo->dirty || entryInfo->shortcutDirty) return true;
247 }
248 return false;
249}
250
251KService::Ptr MenuFolderInfo::findServiceShortcut(const KShortcut&cut)
252{
253 KService::Ptr result;
254 // Check sub-menus
255 foreach (MenuFolderInfo *subFolderInfo, subFolders)
256 {
257 result = subFolderInfo->findServiceShortcut(cut);
258 if (result)
259 return result;
260 }
261
262 // Check entries
263 foreach (MenuEntryInfo *entryInfo, entries)
264 {
265 if (entryInfo->shortCut == cut)
266 return entryInfo->service;
267 }
268 return KService::Ptr();
269}
270
271void MenuFolderInfo::setInUse(bool inUse)
272{
273 // Propagate to sub-menus
274 foreach (MenuFolderInfo *subFolderInfo, subFolders)
275 {
276 subFolderInfo->setInUse(inUse);
277 }
278
279 // Propagate to entries
280 foreach (MenuEntryInfo *entryInfo, entries)
281 {
282 entryInfo->setInUse(inUse);
283 }
284}
285
286//
287// MenuEntryInfo
288//
289
290MenuEntryInfo::~MenuEntryInfo()
291{
292 m_desktopFile->markAsClean();
293 delete m_desktopFile;
294}
295
296KDesktopFile *MenuEntryInfo::desktopFile()
297{
298 if (!m_desktopFile)
299 {
300 m_desktopFile = new KDesktopFile(service->entryPath());
301 }
302 return m_desktopFile;
303}
304
305void MenuEntryInfo::setDirty()
306{
307 if (dirty) return;
308
309 dirty = true;
310
311 QString local = KStandardDirs::locateLocal("xdgdata-apps", service->menuId());
312 if (local != service->entryPath())
313 {
314 KDesktopFile *oldDf = desktopFile();
315 m_desktopFile = oldDf->copyTo(local);
316 delete oldDf;
317 }
318}
319
320bool MenuEntryInfo::needInsertion()
321{
322 // If entry is dirty and previously stored under applnk, then we need to be added explicitly
323 return dirty && !service->entryPath().startsWith('/');
324}
325
326void MenuEntryInfo::save()
327{
328 if (dirty)
329 {
330 m_desktopFile->sync();
331 dirty = false;
332 }
333#ifndef Q_WS_WIN
334 if (shortcutDirty)
335 {
336 if( KHotKeys::present())
337 {
338 KHotKeys::changeMenuEntryShortcut( service->storageId(), shortCut.toString() );
339 }
340 shortcutDirty = false;
341 }
342#endif
343}
344
345void MenuEntryInfo::setCaption(const QString &_caption)
346{
347 if (caption == _caption)
348 return;
349 caption = _caption;
350 setDirty();
351 desktopFile()->desktopGroup().writeEntry("Name", caption);
352}
353
354void MenuEntryInfo::setDescription(const QString &_description)
355{
356 if (description == _description)
357 return;
358 description = _description;
359 setDirty();
360 desktopFile()->desktopGroup().writeEntry("GenericName", description);
361}
362
363void MenuEntryInfo::setIcon(const QString &_icon)
364{
365 if (icon == _icon)
366 return;
367
368 icon = _icon;
369 setDirty();
370 desktopFile()->desktopGroup().writeEntry("Icon", icon);
371}
372
373KShortcut MenuEntryInfo::shortcut()
374{
375#ifndef Q_WS_WIN
376 if (!shortcutLoaded)
377 {
378 shortcutLoaded = true;
379 if( KHotKeys::present())
380 {
381 shortCut = KShortcut(KHotKeys::getMenuEntryShortcut( service->storageId() ));
382 }
383 }
384#endif
385 return shortCut;
386}
387
388static void freeShortcut(const KShortcut &shortCut)
389{
390 if (!shortCut.isEmpty())
391 {
392 QString shortcutKey = shortCut.toString();
393 if (s_newShortcuts)
394 s_newShortcuts->removeAll(shortcutKey);
395
396 if (!s_freeShortcuts)
397 s_freeShortcuts = new QStringList;
398
399 s_freeShortcuts->append(shortcutKey);
400 }
401}
402
403static void allocateShortcut(const KShortcut &shortCut)
404{
405 if (!shortCut.isEmpty())
406 {
407 QString shortcutKey = shortCut.toString();
408 if (s_freeShortcuts)
409 s_freeShortcuts->removeAll(shortcutKey);
410
411 if (!s_newShortcuts)
412 s_newShortcuts = new QStringList;
413
414 s_newShortcuts->append(shortcutKey);
415 }
416}
417
418void MenuEntryInfo::setShortcut(const KShortcut &_shortcut)
419{
420 if (shortCut == _shortcut)
421 return;
422
423 freeShortcut(shortCut);
424 allocateShortcut(_shortcut);
425
426 shortCut = _shortcut;
427 if (shortCut.isEmpty())
428 shortCut = KShortcut(); // Normalize
429
430 shortcutLoaded = true;
431 shortcutDirty = true;
432}
433
434void MenuEntryInfo::setInUse(bool inUse)
435{
436 if (inUse)
437 {
438 KShortcut temp = shortcut();
439 shortCut = KShortcut();
440 if (isShortcutAvailable(temp))
441 shortCut = temp;
442 else
443 shortcutDirty = true;
444 allocateShortcut(shortCut);
445
446 if (s_deletedApps)
447 s_deletedApps->removeAll(service->storageId());
448 }
449 else
450 {
451 freeShortcut(shortcut());
452
453 // Add to list of deleted apps
454 if (!s_deletedApps)
455 s_deletedApps = new QStringList;
456
457 s_deletedApps->append(service->storageId());
458 }
459}
460
461bool MenuEntryInfo::isShortcutAvailable(const KShortcut &_shortcut)
462{
463 // We only have to check agains not saved local shortcuts.
464 // KKeySequenceWidget checks against all other registered shortcuts.
465 if (shortCut == _shortcut)
466 return true;
467
468 QString shortcutKey = _shortcut.toString();
469 bool available = true;
470 if (available && s_newShortcuts)
471 {
472 available = !s_newShortcuts->contains(shortcutKey);
473 }
474 if (!available && s_freeShortcuts)
475 {
476 available = s_freeShortcuts->contains(shortcutKey);
477 }
478 return available;
479}
480