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 }