File indexing completed on 2024-10-06 04:56:10

0001 /*
0002  *   SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-or-later
0005  *
0006  */
0007 
0008 #include "menuinfo.h"
0009 
0010 #include <QRegularExpressionMatch>
0011 
0012 #include <KDesktopFile>
0013 
0014 #include <KConfigGroup>
0015 #include <QStandardPaths>
0016 
0017 #include "globalaccel.h"
0018 #include "menufile.h"
0019 
0020 //
0021 // MenuFolderInfo
0022 //
0023 
0024 static QStringList *s_newShortcuts = nullptr;
0025 static QStringList *s_freeShortcuts = nullptr;
0026 static QStringList *s_deletedApps = nullptr;
0027 
0028 // Add separator
0029 MenuFolderInfo::~MenuFolderInfo()
0030 {
0031     qDeleteAll(subFolders);
0032     subFolders.clear();
0033 }
0034 
0035 void MenuFolderInfo::add(MenuSeparatorInfo *info, bool initial)
0036 {
0037     if (initial) {
0038         initialLayout.append(info);
0039     }
0040 }
0041 
0042 // Add sub menu
0043 void MenuFolderInfo::add(MenuFolderInfo *info, bool initial)
0044 {
0045     subFolders.append(info);
0046     if (initial) {
0047         initialLayout.append(info);
0048     }
0049 }
0050 
0051 // Remove sub menu (without deleting it)
0052 void MenuFolderInfo::take(MenuFolderInfo *info)
0053 {
0054     subFolders.removeAll(info);
0055 }
0056 
0057 // Remove sub menu (without deleting it)
0058 bool MenuFolderInfo::takeRecursive(MenuFolderInfo *info)
0059 {
0060     if (subFolders.removeAll(info) > 0) {
0061         return true;
0062     }
0063 
0064     foreach (MenuFolderInfo *subFolderInfo, subFolders) {
0065         if (subFolderInfo->takeRecursive(info)) {
0066             return true;
0067         }
0068     }
0069     return false;
0070 }
0071 
0072 // Recursively update all fullIds
0073 void MenuFolderInfo::updateFullId(const QString &parentId)
0074 {
0075     fullId = parentId + id;
0076 
0077     foreach (MenuFolderInfo *subFolderInfo, subFolders) {
0078         subFolderInfo->updateFullId(fullId);
0079     }
0080 }
0081 
0082 // Add entry
0083 void MenuFolderInfo::add(MenuEntryInfo *entry, bool initial)
0084 {
0085     entries.append(entry);
0086     if (initial) {
0087         initialLayout.append(entry);
0088     }
0089 }
0090 
0091 // Remove entry
0092 void MenuFolderInfo::take(MenuEntryInfo *entry)
0093 {
0094     entries.removeAll(entry);
0095 }
0096 
0097 QString uniqueCaption(const QString &caption)
0098 {
0099     static const QRegularExpression re(QStringLiteral("(.*)(?=-\\d+)"));
0100     const QRegularExpressionMatch match = re.match(caption);
0101     return match.hasMatch() ? match.captured(1) : caption;
0102 }
0103 
0104 // Return a unique sub-menu caption inspired by @p caption
0105 QString MenuFolderInfo::uniqueMenuCaption(const QString &caption)
0106 {
0107     QString cap = uniqueCaption(caption);
0108 
0109     QString result = caption;
0110 
0111     for (int n = 1; ++n;) {
0112         bool ok = true;
0113         foreach (MenuFolderInfo *subFolderInfo, subFolders) {
0114             if (subFolderInfo->caption == result) {
0115                 ok = false;
0116                 break;
0117             }
0118         }
0119         if (ok) {
0120             return result;
0121         }
0122 
0123         result = cap + QStringLiteral("-%1").arg(n);
0124     }
0125     return QString(); // Never reached
0126 }
0127 
0128 // Return a unique item caption inspired by @p caption
0129 QString MenuFolderInfo::uniqueItemCaption(const QString &caption, const QString &exclude)
0130 {
0131     QString cap = uniqueCaption(caption);
0132 
0133     QString result = caption;
0134 
0135     for (int n = 1; ++n;) {
0136         bool ok = true;
0137         if (result == exclude) {
0138             ok = false;
0139         }
0140         foreach (MenuEntryInfo *entryInfo, entries) {
0141             if (entryInfo->caption == result) {
0142                 ok = false;
0143                 break;
0144             }
0145         }
0146         if (ok) {
0147             return result;
0148         }
0149 
0150         result = cap + QStringLiteral("-%1").arg(n);
0151     }
0152     return QString(); // Never reached
0153 }
0154 
0155 // Return a list of existing submenu ids
0156 QStringList MenuFolderInfo::existingMenuIds()
0157 {
0158     QStringList result;
0159     foreach (MenuFolderInfo *subFolderInfo, subFolders) {
0160         result.append(subFolderInfo->id);
0161     }
0162     return result;
0163 }
0164 
0165 void MenuFolderInfo::setDirty()
0166 {
0167     dirty = true;
0168 }
0169 
0170 void MenuFolderInfo::save(MenuFile *menuFile)
0171 {
0172     if (s_deletedApps) {
0173         // Remove hotkeys for applications that have been deleted
0174         for (QStringList::ConstIterator it = s_deletedApps->constBegin(); it != s_deletedApps->constEnd(); ++it) {
0175             // The shortcut is deleted if we set a empty sequence
0176             GlobalAccel::changeMenuEntryShortcut(KService::serviceByStorageId(*it), QKeySequence());
0177         }
0178         delete s_deletedApps;
0179         s_deletedApps = nullptr;
0180     }
0181 
0182     if (dirty) {
0183         QString local = KDesktopFile::locateLocal(directoryFile);
0184 
0185         KDesktopFile *df = nullptr;
0186         if (directoryFile != local) {
0187             KDesktopFile orig(QStandardPaths::ApplicationsLocation, directoryFile);
0188             df = orig.copyTo(local);
0189         } else {
0190             df = new KDesktopFile(QStandardPaths::ApplicationsLocation, directoryFile);
0191         }
0192 
0193         KConfigGroup dg(df->desktopGroup());
0194         dg.writeEntry("Name", caption);
0195         dg.writeEntry("GenericName", genericname);
0196         dg.writeEntry("Comment", comment);
0197         dg.writeEntry("Icon", icon);
0198         dg.sync();
0199         delete df;
0200         dirty = false;
0201     }
0202 
0203     // Save sub-menus
0204     foreach (MenuFolderInfo *subFolderInfo, subFolders) {
0205         subFolderInfo->save(menuFile);
0206     }
0207 
0208     // Save entries
0209     foreach (MenuEntryInfo *entryInfo, entries) {
0210         if (entryInfo->needInsertion()) {
0211             menuFile->addEntry(fullId, entryInfo->menuId());
0212         }
0213         entryInfo->save();
0214     }
0215 }
0216 
0217 bool MenuFolderInfo::hasDirt()
0218 {
0219     if (dirty) {
0220         return true;
0221     }
0222 
0223     // Check sub-menus
0224     foreach (MenuFolderInfo *subFolderInfo, subFolders) {
0225         if (subFolderInfo->hasDirt()) {
0226             return true;
0227         }
0228     }
0229 
0230     // Check entries
0231     foreach (MenuEntryInfo *entryInfo, entries) {
0232         if (entryInfo->dirty || entryInfo->shortcutDirty) {
0233             return true;
0234         }
0235     }
0236     return false;
0237 }
0238 
0239 KService::Ptr MenuFolderInfo::findServiceShortcut(const QKeySequence &cut)
0240 {
0241     KService::Ptr result;
0242     // Check sub-menus
0243     foreach (MenuFolderInfo *subFolderInfo, subFolders) {
0244         result = subFolderInfo->findServiceShortcut(cut);
0245         if (result) {
0246             return result;
0247         }
0248     }
0249 
0250     // Check entries
0251     foreach (MenuEntryInfo *entryInfo, entries) {
0252         if (entryInfo->shortCut == cut) {
0253             return entryInfo->service;
0254         }
0255     }
0256     return KService::Ptr();
0257 }
0258 
0259 void MenuFolderInfo::setInUse(bool inUse)
0260 {
0261     // Propagate to sub-menus
0262     foreach (MenuFolderInfo *subFolderInfo, subFolders) {
0263         subFolderInfo->setInUse(inUse);
0264     }
0265 
0266     // Propagate to entries
0267     foreach (MenuEntryInfo *entryInfo, entries) {
0268         entryInfo->setInUse(inUse);
0269     }
0270 }
0271 
0272 //
0273 // MenuEntryInfo
0274 //
0275 
0276 MenuEntryInfo::~MenuEntryInfo()
0277 {
0278     m_desktopFile->markAsClean();
0279     delete m_desktopFile;
0280 }
0281 
0282 KDesktopFile *MenuEntryInfo::desktopFile()
0283 {
0284     if (!m_desktopFile) {
0285         m_desktopFile = new KDesktopFile(service->entryPath());
0286     }
0287     return m_desktopFile;
0288 }
0289 
0290 void MenuEntryInfo::setDirty()
0291 {
0292     if (dirty) {
0293         return;
0294     }
0295 
0296     dirty = true;
0297 
0298     QString local = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/') + service->menuId();
0299     if (local != service->entryPath()) {
0300         KDesktopFile *oldDf = desktopFile();
0301         m_desktopFile = oldDf->copyTo(local);
0302         delete oldDf;
0303     }
0304 }
0305 
0306 bool MenuEntryInfo::needInsertion()
0307 {
0308     // If entry is dirty and previously stored under applnk, then we need to be added explicitly
0309     return dirty && !service->entryPath().startsWith(QLatin1Char('/'));
0310 }
0311 
0312 void MenuEntryInfo::save()
0313 {
0314     if (dirty) {
0315         m_desktopFile->sync();
0316         dirty = false;
0317     }
0318     if (shortcutDirty) {
0319         GlobalAccel::changeMenuEntryShortcut(service, shortCut);
0320         shortcutDirty = false;
0321     }
0322 }
0323 
0324 void MenuEntryInfo::setCaption(const QString &_caption)
0325 {
0326     if (caption == _caption) {
0327         return;
0328     }
0329     caption = _caption;
0330     setDirty();
0331     desktopFile()->desktopGroup().writeEntry("Name", caption);
0332 }
0333 
0334 void MenuEntryInfo::setDescription(const QString &_description)
0335 {
0336     if (description == _description) {
0337         return;
0338     }
0339     description = _description;
0340     setDirty();
0341     desktopFile()->desktopGroup().writeEntry("GenericName", description);
0342 }
0343 
0344 void MenuEntryInfo::setIcon(const QString &_icon)
0345 {
0346     if (icon == _icon) {
0347         return;
0348     }
0349 
0350     icon = _icon;
0351     setDirty();
0352     desktopFile()->desktopGroup().writeEntry("Icon", icon);
0353 }
0354 
0355 QKeySequence MenuEntryInfo::shortcut()
0356 {
0357     if (!shortcutLoaded) {
0358         shortcutLoaded = true;
0359         shortCut = GlobalAccel::getMenuEntryShortcut(service);
0360     }
0361     return shortCut;
0362 }
0363 
0364 static void freeShortcut(const QKeySequence &shortCut)
0365 {
0366     if (!shortCut.isEmpty()) {
0367         QString shortcutKey = shortCut.toString();
0368         if (s_newShortcuts) {
0369             s_newShortcuts->removeAll(shortcutKey);
0370         }
0371 
0372         if (!s_freeShortcuts) {
0373             s_freeShortcuts = new QStringList;
0374         }
0375 
0376         s_freeShortcuts->append(shortcutKey);
0377     }
0378 }
0379 
0380 static void allocateShortcut(const QKeySequence &shortCut)
0381 {
0382     if (!shortCut.isEmpty()) {
0383         QString shortcutKey = shortCut.toString();
0384         if (s_freeShortcuts) {
0385             s_freeShortcuts->removeAll(shortcutKey);
0386         }
0387 
0388         if (!s_newShortcuts) {
0389             s_newShortcuts = new QStringList;
0390         }
0391 
0392         s_newShortcuts->append(shortcutKey);
0393     }
0394 }
0395 
0396 void MenuEntryInfo::setShortcut(const QKeySequence &_shortcut)
0397 {
0398     if (shortCut == _shortcut) {
0399         return;
0400     }
0401 
0402     freeShortcut(shortCut);
0403     allocateShortcut(_shortcut);
0404 
0405     shortCut = _shortcut;
0406     if (shortCut.isEmpty()) {
0407         shortCut = QKeySequence(); // Normalize
0408     }
0409     shortcutLoaded = true;
0410     shortcutDirty = true;
0411 }
0412 
0413 void MenuEntryInfo::setInUse(bool inUse)
0414 {
0415     if (inUse) {
0416         QKeySequence temp = shortcut();
0417         shortCut = QKeySequence();
0418         if (isShortcutAvailable(temp)) {
0419             shortCut = temp;
0420         } else {
0421             shortcutDirty = true;
0422         }
0423         allocateShortcut(shortCut);
0424 
0425         if (s_deletedApps) {
0426             s_deletedApps->removeAll(service->storageId());
0427         }
0428     } else {
0429         freeShortcut(shortcut());
0430 
0431         // Add to list of deleted apps
0432         if (!s_deletedApps) {
0433             s_deletedApps = new QStringList;
0434         }
0435 
0436         s_deletedApps->append(service->storageId());
0437     }
0438 }
0439 
0440 bool MenuEntryInfo::isShortcutAvailable(const QKeySequence &_shortcut)
0441 {
0442     // We only have to check agains not saved local shortcuts.
0443     // KKeySequenceWidget checks against all other registered shortcuts.
0444     if (shortCut == _shortcut) {
0445         return true;
0446     }
0447 
0448     QString shortcutKey = _shortcut.toString();
0449     bool available = true;
0450     if (available && s_newShortcuts) {
0451         available = !s_newShortcuts->contains(shortcutKey);
0452     }
0453     if (!available && s_freeShortcuts) {
0454         available = s_freeShortcuts->contains(shortcutKey);
0455     }
0456     return available;
0457 }