diff --git a/menufile.cpp b/menufile.cpp index 24bf847..201afd8 100644 --- a/menufile.cpp +++ b/menufile.cpp @@ -1,546 +1,557 @@ /* * Copyright (C) 2003 Waldo Bastian * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "menufile.h" #include #include #include #include +#include #include "kmenuedit_debug.h" -#include #include -#include const QString MenuFile::MF_MENU = QStringLiteral("Menu"); const QString MenuFile::MF_PUBLIC_ID = QStringLiteral("-//freedesktop//DTD Menu 1.0//EN"); const QString MenuFile::MF_SYSTEM_ID = QStringLiteral("http://www.freedesktop.org/standards/menu-spec/1.0/menu.dtd"); const QString MenuFile::MF_NAME = QStringLiteral("Name"); const QString MenuFile::MF_INCLUDE = QStringLiteral("Include"); const QString MenuFile::MF_EXCLUDE = QStringLiteral("Exclude"); const QString MenuFile::MF_FILENAME = QStringLiteral("Filename"); const QString MenuFile::MF_DELETED = QStringLiteral("Deleted"); const QString MenuFile::MF_NOTDELETED = QStringLiteral("NotDeleted"); const QString MenuFile::MF_MOVE = QStringLiteral("Move"); const QString MenuFile::MF_OLD = QStringLiteral("Old"); const QString MenuFile::MF_NEW = QStringLiteral("New"); const QString MenuFile::MF_DIRECTORY = QStringLiteral("Directory"); const QString MenuFile::MF_LAYOUT = QStringLiteral("Layout"); const QString MenuFile::MF_MENUNAME = QStringLiteral("Menuname"); const QString MenuFile::MF_SEPARATOR = QStringLiteral("Separator"); const QString MenuFile::MF_MERGE = QStringLiteral("Merge"); MenuFile::MenuFile(const QString &file) : m_fileName(file) , m_bDirty(false) { load(); } MenuFile::~MenuFile() { } bool MenuFile::load() { if (m_fileName.isEmpty()) { return false; } QFile file(m_fileName); if (!file.open(QIODevice::ReadOnly)) { if (file.exists()) { qCWarning(KMENUEDIT_LOG) << "Could not read " << m_fileName; } create(); return false; } QString errorMsg; int errorRow; int errorCol; if (!m_doc.setContent(&file, &errorMsg, &errorRow, &errorCol)) { qCWarning(KMENUEDIT_LOG) << "Parse error in " << m_fileName << ", line " << errorRow << ", col " << errorCol << ": " << errorMsg; file.close(); create(); return false; } file.close(); return true; } void MenuFile::create() { QDomImplementation impl; QDomDocumentType docType = impl.createDocumentType(MF_MENU, MF_PUBLIC_ID, MF_SYSTEM_ID); m_doc = impl.createDocument(QString(), MF_MENU, docType); } bool MenuFile::save() { QFile file(m_fileName); if (!file.open(QIODevice::WriteOnly)) { qCWarning(KMENUEDIT_LOG) << "Could not write " << m_fileName; m_error = i18n("Could not write to %1", m_fileName); return false; } QTextStream stream(&file); stream.setCodec("UTF-8"); stream << m_doc.toString(); file.close(); if (file.error() != QFile::NoError) { qCWarning(KMENUEDIT_LOG) << "Could not close " << m_fileName; m_error = i18n("Could not write to %1", m_fileName); return false; } m_bDirty = false; return true; } QDomElement MenuFile::findMenu(QDomElement elem, const QString &menuName, bool create) { QString menuNodeName; QString subMenuName; int i = menuName.indexOf(QLatin1Char('/')); if (i >= 0) { menuNodeName = menuName.left(i); subMenuName = menuName.mid(i+1); } else { menuNodeName = menuName; } if (i == 0) { return findMenu(elem, subMenuName, create); } if (menuNodeName.isEmpty()) { return elem; } QDomNode n = elem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); // try to convert the node to an element. if (e.tagName() == MF_MENU) { QString name; QDomNode n2 = e.firstChild(); while (!n2.isNull()) { QDomElement e2 = n2.toElement(); if (!e2.isNull() && e2.tagName() == MF_NAME) { name = e2.text(); break; } n2 = n2.nextSibling(); } if (name == menuNodeName) { if (subMenuName.isEmpty()) { return e; } else { return findMenu(e, subMenuName, create); } } } n = n.nextSibling(); } if (!create) { return QDomElement(); } // Create new node. QDomElement newElem = m_doc.createElement(MF_MENU); QDomElement newNameElem = m_doc.createElement(MF_NAME); newNameElem.appendChild(m_doc.createTextNode(menuNodeName)); newElem.appendChild(newNameElem); elem.appendChild(newElem); if (subMenuName.isEmpty()) { return newElem; } else { return findMenu(newElem, subMenuName, create); } } +static QString relativeToDesktopDirsLocation(const QString &file) +{ + const QString canonical = QFileInfo(file).canonicalFilePath(); + const QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + for (const QString &dir : dirs) { + const QString base = dir + QLatin1String("/desktop-directories"); + if (canonical.startsWith(base)) + return canonical.mid(base.length()+1); + } + return QString(); +} + static QString entryToDirId(const QString &path) { // See also KDesktopFile::locateLocal QString local; if (QFileInfo(path).isAbsolute()) { // XDG Desktop menu items come with absolute paths, we need to // extract their relative path and then build a local path. - local = KGlobal::dirs()->relativeLocation("xdgdata-dirs", path); + local = relativeToDesktopDirsLocation(path); } if (local.isEmpty() || local.startsWith(QLatin1Char('/'))) { // What now? Use filename only and hope for the best. local = path.mid(path.lastIndexOf(QLatin1Char('/'))+1); } return local; } static void purgeIncludesExcludes(QDomElement elem, const QString &appId, QDomElement &excludeNode, QDomElement &includeNode) { // Remove any previous includes/excludes of appId QDomNode n = elem.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); // try to convert the node to an element. bool bIncludeNode = (e.tagName() == MenuFile::MF_INCLUDE); bool bExcludeNode = (e.tagName() == MenuFile::MF_EXCLUDE); if (bIncludeNode) { includeNode = e; } if (bExcludeNode) { excludeNode = e; } if (bIncludeNode || bExcludeNode) { QDomNode n2 = e.firstChild(); while (!n2.isNull()) { QDomNode next = n2.nextSibling(); QDomElement e2 = n2.toElement(); if (!e2.isNull() && e2.tagName() == MenuFile::MF_FILENAME) { if (e2.text() == appId) { e.removeChild(e2); break; } } n2 = next; } } n = n.nextSibling(); } } static void purgeDeleted(QDomElement elem) { // Remove any previous includes/excludes of appId QDomNode n = elem.firstChild(); while (!n.isNull()) { QDomNode next = n.nextSibling(); QDomElement e = n.toElement(); // try to convert the node to an element. if ((e.tagName() == MenuFile::MF_DELETED) || (e.tagName() == MenuFile::MF_NOTDELETED)) { elem.removeChild(e); } n = next; } } static void purgeLayout(QDomElement elem) { // Remove any previous includes/excludes of appId QDomNode n = elem.firstChild(); while (!n.isNull()) { QDomNode next = n.nextSibling(); QDomElement e = n.toElement(); // try to convert the node to an element. if (e.tagName() == MenuFile::MF_LAYOUT) { elem.removeChild(e); } n = next; } } void MenuFile::addEntry(const QString &menuName, const QString &menuId) { m_bDirty = true; m_removedEntries.removeAll(menuId); QDomElement elem = findMenu(m_doc.documentElement(), menuName, true); QDomElement excludeNode; QDomElement includeNode; purgeIncludesExcludes(elem, menuId, excludeNode, includeNode); if (includeNode.isNull()) { includeNode = m_doc.createElement(MF_INCLUDE); elem.appendChild(includeNode); } QDomElement fileNode = m_doc.createElement(MF_FILENAME); fileNode.appendChild(m_doc.createTextNode(menuId)); includeNode.appendChild(fileNode); } void MenuFile::setLayout(const QString &menuName, const QStringList &layout) { m_bDirty = true; QDomElement elem = findMenu(m_doc.documentElement(), menuName, true); purgeLayout(elem); QDomElement layoutNode = m_doc.createElement(MF_LAYOUT); elem.appendChild(layoutNode); for (QStringList::ConstIterator it = layout.constBegin(); it != layout.constEnd(); ++it) { QString li = *it; if (li == QLatin1String(":S")) { layoutNode.appendChild(m_doc.createElement(MF_SEPARATOR)); } else if (li == QLatin1String(":M")) { QDomElement mergeNode = m_doc.createElement(MF_MERGE); mergeNode.setAttribute(QStringLiteral("type"), QStringLiteral("menus")); layoutNode.appendChild(mergeNode); } else if (li == QLatin1String(":F")) { QDomElement mergeNode = m_doc.createElement(MF_MERGE); mergeNode.setAttribute(QStringLiteral("type"), QStringLiteral("files")); layoutNode.appendChild(mergeNode); } else if (li == QLatin1String(":A")) { QDomElement mergeNode = m_doc.createElement(MF_MERGE); mergeNode.setAttribute(QStringLiteral("type"), QStringLiteral("all")); layoutNode.appendChild(mergeNode); } else if (li.endsWith(QLatin1Char('/'))) { li.chop(1); QDomElement menuNode = m_doc.createElement(MF_MENUNAME); menuNode.appendChild(m_doc.createTextNode(li)); layoutNode.appendChild(menuNode); } else { QDomElement fileNode = m_doc.createElement(MF_FILENAME); fileNode.appendChild(m_doc.createTextNode(li)); layoutNode.appendChild(fileNode); } } } void MenuFile::removeEntry(const QString &menuName, const QString &menuId) { m_bDirty = true; m_removedEntries.append(menuId); QDomElement elem = findMenu(m_doc.documentElement(), menuName, true); QDomElement excludeNode; QDomElement includeNode; purgeIncludesExcludes(elem, menuId, excludeNode, includeNode); if (excludeNode.isNull()) { excludeNode = m_doc.createElement(MF_EXCLUDE); elem.appendChild(excludeNode); } QDomElement fileNode = m_doc.createElement(MF_FILENAME); fileNode.appendChild(m_doc.createTextNode(menuId)); excludeNode.appendChild(fileNode); } void MenuFile::addMenu(const QString &menuName, const QString &menuFile) { m_bDirty = true; QDomElement elem = findMenu(m_doc.documentElement(), menuName, true); QDomElement dirElem = m_doc.createElement(MF_DIRECTORY); dirElem.appendChild(m_doc.createTextNode(entryToDirId(menuFile))); elem.appendChild(dirElem); } void MenuFile::moveMenu(const QString &oldMenu, const QString &newMenu) { m_bDirty = true; // Undelete the new menu QDomElement elem = findMenu(m_doc.documentElement(), newMenu, true); purgeDeleted(elem); elem.appendChild(m_doc.createElement(MF_NOTDELETED)); // TODO: GET RID OF COMMON PART, IT BREAKS STUFF // Find common part QStringList oldMenuParts = oldMenu.split(QLatin1Char('/')); QStringList newMenuParts = newMenu.split(QLatin1Char('/')); QString commonMenuName; int max = qMin(oldMenuParts.count(), newMenuParts.count()); int i = 0; for (; i < max; i++) { if (oldMenuParts[i] != newMenuParts[i]) { break; } commonMenuName += QLatin1Char('/') + oldMenuParts[i]; } QString oldMenuName; for (int j = i; j < oldMenuParts.count()-1; j++) { if (i != j) { oldMenuName += QLatin1Char('/'); } oldMenuName += oldMenuParts[j]; } QString newMenuName; for (int j = i; j < newMenuParts.count()-1; j++) { if (i != j) { newMenuName += QLatin1Char('/'); } newMenuName += newMenuParts[j]; } if (oldMenuName == newMenuName) { return; // Can happen } elem = findMenu(m_doc.documentElement(), commonMenuName, true); // Add instructions for moving QDomElement moveNode = m_doc.createElement(MF_MOVE); QDomElement node = m_doc.createElement(MF_OLD); node.appendChild(m_doc.createTextNode(oldMenuName)); moveNode.appendChild(node); node = m_doc.createElement(MF_NEW); node.appendChild(m_doc.createTextNode(newMenuName)); moveNode.appendChild(node); elem.appendChild(moveNode); } void MenuFile::removeMenu(const QString &menuName) { m_bDirty = true; QDomElement elem = findMenu(m_doc.documentElement(), menuName, true); purgeDeleted(elem); elem.appendChild(m_doc.createElement(MF_DELETED)); } /** * Returns a unique menu-name for a new menu under @p menuName * inspired by @p newMenu */ QString MenuFile::uniqueMenuName(const QString &menuName, const QString &newMenu, const QStringList &excludeList) { QDomElement elem = findMenu(m_doc.documentElement(), menuName, false); QString result = newMenu; if (result.endsWith(QLatin1Char('/'))) { result.chop(1); } QRegExp r(QStringLiteral("(.*)(?=-\\d+)")); result = (r.indexIn(result) > -1) ? r.cap(1) : result; int trunc = result.length(); // Position of trailing '/' result.append(QLatin1Char('/')); for (int n = 1; ++n;) { if (findMenu(elem, result, false).isNull() && !excludeList.contains(result)) { return result; } result.truncate(trunc); result.append(QStringLiteral("-%1/").arg(n)); } return QString(); // Never reached } void MenuFile::performAction(const ActionAtom *atom) { switch (atom->action) { case ADD_ENTRY: addEntry(atom->arg1, atom->arg2); return; case REMOVE_ENTRY: removeEntry(atom->arg1, atom->arg2); return; case ADD_MENU: addMenu(atom->arg1, atom->arg2); return; case REMOVE_MENU: removeMenu(atom->arg1); return; case MOVE_MENU: moveMenu(atom->arg1, atom->arg2); return; } } MenuFile::ActionAtom *MenuFile::pushAction(MenuFile::ActionType action, const QString &arg1, const QString &arg2) { ActionAtom *atom = new ActionAtom; atom->action = action; atom->arg1 = arg1; atom->arg2 = arg2; m_actionList.append(atom); return atom; } void MenuFile::popAction(ActionAtom *atom) { if (m_actionList.last() != atom) { qWarning("MenuFile::popAction Error, action not last in list."); return; } m_actionList.removeLast(); delete atom; } bool MenuFile::performAllActions() { Q_FOREACH (ActionAtom *atom, m_actionList) { performAction(atom); delete atom; } m_actionList.clear(); // Entries that have been removed from the menu are added to .hidden // so that they don't re-appear in Lost & Found QStringList removed = m_removedEntries; m_removedEntries.clear(); for (QStringList::ConstIterator it = removed.constBegin(); it != removed.constEnd(); ++it) { addEntry(QStringLiteral("/.hidden/"), *it); } m_removedEntries.clear(); if (!m_bDirty) { return true; } return save(); } bool MenuFile::dirty() const { return (m_actionList.count() != 0) || m_bDirty; } void MenuFile::restoreMenuSystem(const QString &filename) { m_error.clear(); m_fileName = filename; m_doc.clear(); m_bDirty = false; Q_FOREACH (ActionAtom *atom, m_actionList) { delete atom; } m_actionList.clear(); m_removedEntries.clear(); create(); } diff --git a/menuinfo.cpp b/menuinfo.cpp index 3a1415d..2ba2ce6 100644 --- a/menuinfo.cpp +++ b/menuinfo.cpp @@ -1,476 +1,477 @@ /* * Copyright (C) 2003 Waldo Bastian * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "menuinfo.h" #include #include -#include + #include +#include #include "menufile.h" #ifdef WITH_HOTKEYS #include "khotkeys.h" #endif // // MenuFolderInfo // static QStringList *s_newShortcuts = nullptr; static QStringList *s_freeShortcuts = nullptr; static QStringList *s_deletedApps = nullptr; // Add separator MenuFolderInfo::~MenuFolderInfo() { qDeleteAll(subFolders); subFolders.clear(); } void MenuFolderInfo::add(MenuSeparatorInfo *info, bool initial) { if (initial) { initialLayout.append(info); } } // Add sub menu void MenuFolderInfo::add(MenuFolderInfo *info, bool initial) { subFolders.append(info); if (initial) { initialLayout.append(info); } } // Remove sub menu (without deleting it) void MenuFolderInfo::take(MenuFolderInfo *info) { subFolders.removeAll(info); } // Remove sub menu (without deleting it) bool MenuFolderInfo::takeRecursive(MenuFolderInfo *info) { if (subFolders.removeAll(info) > 0) { return true; } foreach (MenuFolderInfo *subFolderInfo, subFolders) { if (subFolderInfo->takeRecursive(info)) { return true; } } return false; } // Recursively update all fullIds void MenuFolderInfo::updateFullId(const QString &parentId) { fullId = parentId + id; foreach (MenuFolderInfo *subFolderInfo, subFolders) { subFolderInfo->updateFullId(fullId); } } // Add entry void MenuFolderInfo::add(MenuEntryInfo *entry, bool initial) { entries.append(entry); if (initial) { initialLayout.append(entry); } } // Remove entry void MenuFolderInfo::take(MenuEntryInfo *entry) { entries.removeAll(entry); } // Return a unique sub-menu caption inspired by @p caption QString MenuFolderInfo::uniqueMenuCaption(const QString &caption) { QRegExp r(QStringLiteral("(.*)(?=-\\d+)")); QString cap = (r.indexIn(caption) > -1) ? r.cap(1) : caption; QString result = caption; for (int n = 1; ++n;) { bool ok = true; foreach (MenuFolderInfo *subFolderInfo, subFolders) { if (subFolderInfo->caption == result) { ok = false; break; } } if (ok) { return result; } result = cap + QStringLiteral("-%1").arg(n); } return QString(); // Never reached } // Return a unique item caption inspired by @p caption QString MenuFolderInfo::uniqueItemCaption(const QString &caption, const QString &exclude) { QRegExp r(QStringLiteral("(.*)(?=-\\d+)")); QString cap = (r.indexIn(caption) > -1) ? r.cap(1) : caption; QString result = caption; for (int n = 1; ++n;) { bool ok = true; if (result == exclude) { ok = false; } foreach (MenuEntryInfo *entryInfo, entries) { if (entryInfo->caption == result) { ok = false; break; } } if (ok) { return result; } result = cap + QStringLiteral("-%1").arg(n); } return QString(); // Never reached } // Return a list of existing submenu ids QStringList MenuFolderInfo::existingMenuIds() { QStringList result; foreach (MenuFolderInfo *subFolderInfo, subFolders) { result.append(subFolderInfo->id); } return result; } void MenuFolderInfo::setDirty() { dirty = true; } void MenuFolderInfo::save(MenuFile *menuFile) { if (s_deletedApps) { #ifdef WITH_HOTKEYS // Remove hotkeys for applications that have been deleted for (QStringList::ConstIterator it = s_deletedApps->constBegin(); it != s_deletedApps->constEnd(); ++it) { // The shorcut is deleted if we set a empty sequence KHotKeys::changeMenuEntryShortcut(*it, QLatin1String("")); } #endif delete s_deletedApps; s_deletedApps = nullptr; } if (dirty) { QString local = KDesktopFile::locateLocal(directoryFile); KDesktopFile *df = nullptr; if (directoryFile != local) { KDesktopFile orig(QStandardPaths::ApplicationsLocation, directoryFile); df = orig.copyTo(local); } else { df = new KDesktopFile(QStandardPaths::ApplicationsLocation, directoryFile); } KConfigGroup dg(df->desktopGroup()); dg.writeEntry("Name", caption); dg.writeEntry("GenericName", genericname); dg.writeEntry("Comment", comment); dg.writeEntry("Icon", icon); dg.sync(); delete df; dirty = false; } // Save sub-menus foreach (MenuFolderInfo *subFolderInfo, subFolders) { subFolderInfo->save(menuFile); } // Save entries foreach (MenuEntryInfo *entryInfo, entries) { if (entryInfo->needInsertion()) { menuFile->addEntry(fullId, entryInfo->menuId()); } entryInfo->save(); } } bool MenuFolderInfo::hasDirt() { if (dirty) { return true; } // Check sub-menus foreach (MenuFolderInfo *subFolderInfo, subFolders) { if (subFolderInfo->hasDirt()) { return true; } } // Check entries foreach (MenuEntryInfo *entryInfo, entries) { if (entryInfo->dirty || entryInfo->shortcutDirty) { return true; } } return false; } KService::Ptr MenuFolderInfo::findServiceShortcut(const QKeySequence &cut) { KService::Ptr result; // Check sub-menus foreach (MenuFolderInfo *subFolderInfo, subFolders) { result = subFolderInfo->findServiceShortcut(cut); if (result) { return result; } } // Check entries foreach (MenuEntryInfo *entryInfo, entries) { if (entryInfo->shortCut == cut) { return entryInfo->service; } } return KService::Ptr(); } void MenuFolderInfo::setInUse(bool inUse) { // Propagate to sub-menus foreach (MenuFolderInfo *subFolderInfo, subFolders) { subFolderInfo->setInUse(inUse); } // Propagate to entries foreach (MenuEntryInfo *entryInfo, entries) { entryInfo->setInUse(inUse); } } // // MenuEntryInfo // MenuEntryInfo::~MenuEntryInfo() { m_desktopFile->markAsClean(); delete m_desktopFile; } KDesktopFile *MenuEntryInfo::desktopFile() { if (!m_desktopFile) { m_desktopFile = new KDesktopFile(service->entryPath()); } return m_desktopFile; } void MenuEntryInfo::setDirty() { if (dirty) { return; } dirty = true; - QString local = KStandardDirs::locateLocal("xdgdata-apps", service->menuId()); + QString local = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + QLatin1Char('/') + service->menuId(); if (local != service->entryPath()) { KDesktopFile *oldDf = desktopFile(); m_desktopFile = oldDf->copyTo(local); delete oldDf; } } bool MenuEntryInfo::needInsertion() { // If entry is dirty and previously stored under applnk, then we need to be added explicitly return dirty && !service->entryPath().startsWith(QLatin1Char('/')); } void MenuEntryInfo::save() { if (dirty) { m_desktopFile->sync(); dirty = false; } #ifdef WITH_HOTKEYS if (shortcutDirty) { if (KHotKeys::present()) { KHotKeys::changeMenuEntryShortcut(service->storageId(), shortCut.toString()); } shortcutDirty = false; } #endif } void MenuEntryInfo::setCaption(const QString &_caption) { if (caption == _caption) { return; } caption = _caption; setDirty(); desktopFile()->desktopGroup().writeEntry("Name", caption); } void MenuEntryInfo::setDescription(const QString &_description) { if (description == _description) { return; } description = _description; setDirty(); desktopFile()->desktopGroup().writeEntry("GenericName", description); } void MenuEntryInfo::setIcon(const QString &_icon) { if (icon == _icon) { return; } icon = _icon; setDirty(); desktopFile()->desktopGroup().writeEntry("Icon", icon); } QKeySequence MenuEntryInfo::shortcut() { #ifdef WITH_HOTKEYS if (!shortcutLoaded) { shortcutLoaded = true; if (KHotKeys::present()) { shortCut = QKeySequence(KHotKeys::getMenuEntryShortcut(service->storageId())); } } #endif return shortCut; } static void freeShortcut(const QKeySequence &shortCut) { if (!shortCut.isEmpty()) { QString shortcutKey = shortCut.toString(); if (s_newShortcuts) { s_newShortcuts->removeAll(shortcutKey); } if (!s_freeShortcuts) { s_freeShortcuts = new QStringList; } s_freeShortcuts->append(shortcutKey); } } static void allocateShortcut(const QKeySequence &shortCut) { if (!shortCut.isEmpty()) { QString shortcutKey = shortCut.toString(); if (s_freeShortcuts) { s_freeShortcuts->removeAll(shortcutKey); } if (!s_newShortcuts) { s_newShortcuts = new QStringList; } s_newShortcuts->append(shortcutKey); } } void MenuEntryInfo::setShortcut(const QKeySequence &_shortcut) { if (shortCut == _shortcut) { return; } freeShortcut(shortCut); allocateShortcut(_shortcut); shortCut = _shortcut; if (shortCut.isEmpty()) { shortCut = QKeySequence(); // Normalize } shortcutLoaded = true; shortcutDirty = true; } void MenuEntryInfo::setInUse(bool inUse) { if (inUse) { QKeySequence temp = shortcut(); shortCut = QKeySequence(); if (isShortcutAvailable(temp)) { shortCut = temp; } else { shortcutDirty = true; } allocateShortcut(shortCut); if (s_deletedApps) { s_deletedApps->removeAll(service->storageId()); } } else { freeShortcut(shortcut()); // Add to list of deleted apps if (!s_deletedApps) { s_deletedApps = new QStringList; } s_deletedApps->append(service->storageId()); } } bool MenuEntryInfo::isShortcutAvailable(const QKeySequence &_shortcut) { // We only have to check agains not saved local shortcuts. // KKeySequenceWidget checks against all other registered shortcuts. if (shortCut == _shortcut) { return true; } QString shortcutKey = _shortcut.toString(); bool available = true; if (available && s_newShortcuts) { available = !s_newShortcuts->contains(shortcutKey); } if (!available && s_freeShortcuts) { available = s_freeShortcuts->contains(shortcutKey); } return available; } diff --git a/treeview.cpp b/treeview.cpp index 45d28ba..8c7bdf0 100644 --- a/treeview.cpp +++ b/treeview.cpp @@ -1,1887 +1,1890 @@ /* * Copyright (C) 2000 Matthias Elter * Copyright (C) 2001-2002 Raffaele Sandrini * Copyright (C) 2003 Waldo Bastian * Copyright (C) 2008 Laurent Montel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "treeview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kmenuedit_debug.h" #include -#include #include #include #include #include #include #include #include #include -#include -#include #include #include #include +#include #include "menufile.h" #include "menuinfo.h" #define MOVE_FOLDER 'M' #define COPY_FOLDER 'C' #define MOVE_FILE 'm' #define COPY_FILE 'c' #define COPY_SEPARATOR 'S' static const char *s_internalMimeType = "application/x-kmenuedit-internal"; class SeparatorWidget : public QWidget { public: SeparatorWidget() : QWidget(nullptr) { } protected: void paintEvent(QPaintEvent * /*event*/) override { QPainter p(this); // Draw Separator int h = (height() / 2) -1; // if (isSelected()) { // p->setPen( palette().color( QPalette::HighlightedText ) ); // } else { // p->setPen( palette().color( QPalette::Text ) ); // } p.drawLine(2, h, width() - 4, h); } }; TreeItem::TreeItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, const QString &menuId, bool _m_init) : QTreeWidgetItem(parent, after) , m_hidden(false) , m_init(_m_init) , m_layoutDirty(false) , m_menuId(menuId) , m_folderInfo(nullptr) , m_entryInfo(nullptr) { } TreeItem::TreeItem(QTreeWidget *parent, QTreeWidgetItem *after, const QString &menuId, bool _m_init) : QTreeWidgetItem(parent, after) , m_hidden(false) , m_init(_m_init) , m_layoutDirty(false) , m_menuId(menuId) , m_folderInfo(nullptr) , m_entryInfo(nullptr) { load(); } TreeItem::~TreeItem() { } /** * @brief Return the description. * @return Description, or an empty string if none. */ QString TreeItem::description() const { QString description; if (isEntry()) { description = entryInfo()->description; } return description; } /** * @brief Compare two items using their names. * @param item1 First item. * @param item2 Second item. * @return Integer less than, equal to, or greater than zero if item1 is less than, equal to, or greater than item2. */ bool TreeItem::itemNameLessThan(QTreeWidgetItem *item1, QTreeWidgetItem *item2) { TreeItem *treeItem1 = static_cast(item1); TreeItem *treeItem2 = static_cast(item2); return QString::localeAwareCompare(treeItem1->name(), treeItem2->name()) < 0; } /** * @brief Compare two items using their descriptions. If both are empty, sort them by name. * @param item1 First item. * @param item2 Second item. * @return Integer less than, equal to, or greater than zero if item1 is less than, equal to, or greater than item2. */ bool TreeItem::itemDescriptionLessThan(QTreeWidgetItem *item1, QTreeWidgetItem *item2) { // extract descriptions in lower case TreeItem *treeItem1 = static_cast(item1); TreeItem *treeItem2 = static_cast(item2); const QString description1 = treeItem1->description(); const QString description2 = treeItem2->description(); // if description is missing for both items, sort them using their names if (description1.isEmpty() && description2.isEmpty()) { return itemNameLessThan(item1, item2); } else { return QString::localeAwareCompare(description1, description2) < 0; } } void TreeItem::setName(const QString &name) { if (m_name == name) { return; } m_name = name; update(); } void TreeItem::setHiddenInMenu(bool b) { if (m_hidden == b) { return; } m_hidden = b; update(); } void TreeItem::update() { QString s = m_name; if (m_hidden) { s += i18n(" [Hidden]"); } setText(0, s); } void TreeItem::load() { if (m_folderInfo && !m_init) { m_init = true; TreeView *tv = static_cast(treeWidget()); tv->fillBranch(m_folderInfo, this); } } bool TreeItem::isLayoutDirty() const { if (m_layoutDirty) { return true; } for (int i = 0; i < childCount(); ++i) { TreeItem *item = dynamic_cast(child(i)); if (!item) { continue; } if (item->isLayoutDirty()) { return true; } } return false; } TreeView::TreeView(KActionCollection *ac, QWidget *parent) : QTreeWidget(parent) , m_ac(ac) , m_popupMenu(nullptr) , m_clipboard(0) , m_clipboardFolderInfo(nullptr) , m_clipboardEntryInfo(nullptr) , m_layoutDirty(false) , m_detailedMenuEntries(true) , m_detailedEntriesNamesFirst(true) { m_dropMimeTypes << QLatin1String(s_internalMimeType) << KUrlMimeData::mimeDataTypes(); qRegisterMetaType("TreeItem"); setAllColumnsShowFocus(true); setRootIsDecorated(true); setSortingEnabled(false); setDragEnabled(true); setAcceptDrops(true); setMinimumWidth(240); setHeaderLabels(QStringList() << QLatin1String("")); header()->hide(); // listen for creation connect(m_ac->action(NEW_ITEM_ACTION_NAME), &QAction::triggered, this, &TreeView::newitem); connect(m_ac->action(NEW_SUBMENU_ACTION_NAME), &QAction::triggered, this, &TreeView::newsubmenu); connect(m_ac->action(NEW_SEPARATOR_ACTION_NAME), &QAction::triggered, this, &TreeView::newsep); // listen for copy connect(m_ac->action(CUT_ACTION_NAME), &QAction::triggered, this, &TreeView::cut); connect(m_ac->action(COPY_ACTION_NAME), SIGNAL(triggered()), SLOT(copy())); connect(m_ac->action(PASTE_ACTION_NAME), &QAction::triggered, this, &TreeView::paste); // listen for deleting connect(m_ac->action(DELETE_ACTION_NAME), SIGNAL(triggered()), SLOT(del())); // listen for sorting m_sortSignalMapper = new QSignalMapper(this); QAction *action = m_ac->action(SORT_BY_NAME_ACTION_NAME); connect(action, SIGNAL(triggered()), m_sortSignalMapper, SLOT(map())); m_sortSignalMapper->setMapping(action, SortByName); action = m_ac->action(SORT_BY_DESCRIPTION_ACTION_NAME); connect(action, SIGNAL(triggered()), m_sortSignalMapper, SLOT(map())); m_sortSignalMapper->setMapping(action, SortByDescription); action = m_ac->action(SORT_ALL_BY_NAME_ACTION_NAME); connect(action, SIGNAL(triggered()), m_sortSignalMapper, SLOT(map())); m_sortSignalMapper->setMapping(action, SortAllByName); action = m_ac->action(SORT_ALL_BY_DESCRIPTION_ACTION_NAME); connect(action, SIGNAL(triggered()), m_sortSignalMapper, SLOT(map())); m_sortSignalMapper->setMapping(action, SortAllByDescription); connect(m_sortSignalMapper, SIGNAL(mapped(const int)), this, SLOT(sort(const int))); // connect moving up/down actions connect(m_ac->action(MOVE_UP_ACTION_NAME), &QAction::triggered, this, &TreeView::moveUpItem); connect(m_ac->action(MOVE_DOWN_ACTION_NAME), &QAction::triggered, this, &TreeView::moveDownItem); // listen for selection connect(this, &QTreeWidget::currentItemChanged, this, &TreeView::itemSelected); - m_menuFile = new MenuFile(KStandardDirs::locateLocal("xdgconf-menu", QStringLiteral("applications-kmenuedit.menu"))); + m_menuFile = new MenuFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/menus/") + QStringLiteral("applications-kmenuedit.menu")); m_rootFolder = new MenuFolderInfo; m_separator = new MenuSeparatorInfo; } TreeView::~TreeView() { cleanupClipboard(); delete m_rootFolder; delete m_separator; delete m_menuFile; } void TreeView::setViewMode(bool showHidden) { // setup popup menu delete m_popupMenu; m_popupMenu = new QMenu(this); // creation m_popupMenu->addAction(m_ac->action(NEW_ITEM_ACTION_NAME)); m_popupMenu->addAction(m_ac->action(NEW_SUBMENU_ACTION_NAME)); m_popupMenu->addAction(m_ac->action(NEW_SEPARATOR_ACTION_NAME)); m_popupMenu->addSeparator(); // copy m_popupMenu->addAction(m_ac->action(CUT_ACTION_NAME)); m_popupMenu->addAction(m_ac->action(COPY_ACTION_NAME)); m_popupMenu->addAction(m_ac->action(PASTE_ACTION_NAME)); m_popupMenu->addSeparator(); // delete m_popupMenu->addAction(m_ac->action(DELETE_ACTION_NAME)); m_popupMenu->addSeparator(); // move m_popupMenu->addAction(m_ac->action(MOVE_UP_ACTION_NAME)); m_popupMenu->addAction(m_ac->action(MOVE_DOWN_ACTION_NAME)); m_popupMenu->addSeparator(); // sort m_popupMenu->addAction(m_ac->action(SORT_ACTION_NAME)); m_showHidden = showHidden; readMenuFolderInfo(); fill(); } void TreeView::readMenuFolderInfo(MenuFolderInfo *folderInfo, KServiceGroup::Ptr folder, const QString &prefix) { if (!folderInfo) { folderInfo = m_rootFolder; folder = KServiceGroup::root(); } if (!folder || !folder->isValid()) { return; } folderInfo->caption = folder->caption(); folderInfo->comment = folder->comment(); // Item names may contain ampersands. To avoid them being converted // to accelerators, replace them with two ampersands. folderInfo->hidden = folder->noDisplay(); folderInfo->directoryFile = folder->directoryEntryPath(); folderInfo->icon = folder->icon(); QString id = folder->relPath(); int i = id.lastIndexOf(QLatin1Char('/'), -2); id = id.mid(i+1); folderInfo->id = id; folderInfo->fullId = prefix + id; for (const KSycocaEntry::Ptr &e : folder->entries(true, !m_showHidden, true, m_detailedMenuEntries && !m_detailedEntriesNamesFirst)) { if (e->isType(KST_KServiceGroup)) { const KServiceGroup::Ptr serviceGroup(static_cast(e.data())); MenuFolderInfo *subFolderInfo = new MenuFolderInfo(); readMenuFolderInfo(subFolderInfo, serviceGroup, folderInfo->fullId); folderInfo->add(subFolderInfo, true); } else if (e->isType(KST_KService)) { const KService::Ptr service(static_cast(e.data())); folderInfo->add(new MenuEntryInfo(service), true); } else if (e->isType(KST_KServiceSeparator)) { folderInfo->add(m_separator, true); } } } void TreeView::fill() { QApplication::setOverrideCursor(Qt::WaitCursor); clear(); fillBranch(m_rootFolder, nullptr); QApplication::restoreOverrideCursor(); } QString TreeView::findName(KDesktopFile *df, bool deleted) { QString name = df->readName(); if (deleted) { if (name == QLatin1String("empty")) { name.clear(); } if (name.isEmpty()) { bool isLocal = true; const QStringList files = QStandardPaths::locateAll(df->resource(), df->fileName(), QStandardPaths::LocateFile); for (QStringList::ConstIterator it = files.constBegin(); it != files.constEnd(); ++it) { if (isLocal) { isLocal = false; continue; } KDesktopFile df2(*it); name = df2.readName(); if (!name.isEmpty() && (name != QLatin1String("empty"))) { return name; } } } } return name; } TreeItem *TreeView::createTreeItem(TreeItem *parent, QTreeWidgetItem *after, MenuFolderInfo *folderInfo, bool m_init) { TreeItem *item; if (parent) { item = new TreeItem(parent, after, QString(), m_init); } else { item = new TreeItem(this, after, QString(), m_init); } item->setMenuFolderInfo(folderInfo); item->setName(folderInfo->caption); item->setIcon(0, QIcon::fromTheme(folderInfo->icon)); item->setDirectoryPath(folderInfo->fullId); item->setHiddenInMenu(folderInfo->hidden); item->load(); return item; } TreeItem *TreeView::createTreeItem(TreeItem *parent, QTreeWidgetItem *after, MenuEntryInfo *entryInfo, bool m_init) { bool hidden = entryInfo->hidden; TreeItem *item; if (parent) { item = new TreeItem(parent, after, entryInfo->menuId(), m_init); } else { item = new TreeItem(this, after, entryInfo->menuId(), m_init); } QString name; if (m_detailedMenuEntries && entryInfo->description.length() != 0) { if (m_detailedEntriesNamesFirst) { name = entryInfo->caption + QLatin1String(" (") + entryInfo->description + QLatin1Char(')'); } else { name = entryInfo->description + QLatin1String(" (") + entryInfo->caption + QLatin1Char(')'); } } else { name = entryInfo->caption; } ////qCDebug(KMENUEDIT_LOG) << parent << after << name; item->setMenuEntryInfo(entryInfo); item->setName(name); item->setIcon(0, QIcon::fromTheme(entryInfo->icon)); item->setHiddenInMenu(hidden); item->load(); return item; } TreeItem *TreeView::createTreeItem(TreeItem *parent, QTreeWidgetItem *after, MenuSeparatorInfo *, bool init) { TreeItem *item; if (parent) { item = new TreeItem(parent, after, QString(), init); } else { item = new TreeItem(this, after, QString(), init); } setItemWidget(item, 0, new SeparatorWidget); return item; } void TreeView::fillBranch(MenuFolderInfo *folderInfo, TreeItem *parent) { QString relPath = parent ? parent->directory() : QString(); TreeItem *after = nullptr; foreach (MenuInfo *info, folderInfo->initialLayout) { MenuEntryInfo *entry = dynamic_cast(info); if (entry) { after = createTreeItem(parent, after, entry); continue; } MenuFolderInfo *subFolder = dynamic_cast(info); if (subFolder) { after = createTreeItem(parent, after, subFolder); continue; } MenuSeparatorInfo *separator = dynamic_cast(info); if (separator) { after = createTreeItem(parent, after, separator); continue; } } } TreeItem *TreeView::expandPath(TreeItem *item, const QString &path) { int i = path.indexOf(QLatin1String("/")); QString subMenu = path.left(i+1); QString restMenu = path.mid(i+1); for (int i = 0; i < item->childCount(); ++i) { TreeItem *childItem = dynamic_cast(item->child(i)); if (!childItem) { continue; } MenuFolderInfo *folderInfo = childItem->folderInfo(); if (folderInfo && (folderInfo->id == subMenu)) { childItem->setExpanded(true); if (!restMenu.isEmpty()) { return expandPath(childItem, restMenu); } else { return childItem; } } } return nullptr; } void TreeView::selectMenu(const QString &menu) { // close all parent expansions and deselect everything collapseAll(); setCurrentIndex(rootIndex()); if (menu.length() <= 1) { setCurrentItem(topLevelItem(0)); clearSelection(); return; // Root menu } QString restMenu = menu; if (menu.startsWith(QLatin1Char('/'))) { restMenu = menu.mid(1); } if (!restMenu.endsWith(QLatin1Char('/'))) { restMenu += QLatin1Char('/'); } TreeItem *item = nullptr; int i = restMenu.indexOf(QLatin1String("/")); QString subMenu = restMenu.left(i+1); restMenu = restMenu.mid(i+1); for (int i = 0; i < topLevelItemCount(); ++i) { item = dynamic_cast(topLevelItem(i)); if (!item) { continue; } MenuFolderInfo *folderInfo = item->folderInfo(); if (folderInfo && (folderInfo->id == subMenu)) { if (!restMenu.isEmpty()) { item = expandPath(item, restMenu); } break; } } if (item) { setCurrentItem(item); scrollToItem(item); } } void TreeView::selectMenuEntry(const QString &menuEntry) { TreeItem *item = static_cast(selectedItem()); if (!item) { item = static_cast(currentItem()); } if (!item) { return; } QTreeWidgetItem *parent = item->parent(); if (parent) { for (int i = 0; i < parent->childCount(); ++i) { TreeItem *item = dynamic_cast(parent->child(i)); if (!item || item->isDirectory()) { continue; } MenuEntryInfo *entry = item->entryInfo(); if (entry && entry->menuId() == menuEntry) { setCurrentItem(item); scrollToItem(item); return; } } } else { // top level for (int i = 0; i < topLevelItemCount(); ++i) { TreeItem *item = dynamic_cast(topLevelItem(i)); if (!item || item->isDirectory()) { continue; } MenuEntryInfo *entry = item->entryInfo(); if (entry && entry->menuId() == menuEntry) { setCurrentItem(item); scrollToItem(item); return; } } } } void TreeView::itemSelected(QTreeWidgetItem *item) { // ensure the item is visible as selected setItemSelected(item, true); TreeItem *_item = static_cast(item); TreeItem *parentItem = nullptr; bool selected = false; bool dselected = false; if (_item) { selected = true; dselected = _item->isHiddenInMenu(); parentItem = getParentItem(_item); } // change actions activation m_ac->action(CUT_ACTION_NAME)->setEnabled(selected); m_ac->action(COPY_ACTION_NAME)->setEnabled(selected); m_ac->action(PASTE_ACTION_NAME)->setEnabled(m_clipboard != 0); if (m_ac->action(DELETE_ACTION_NAME)) { m_ac->action(DELETE_ACTION_NAME)->setEnabled(selected && !dselected); } m_ac->action(SORT_BY_NAME_ACTION_NAME)->setEnabled(selected && _item->isDirectory() && (_item->childCount() > 0)); m_ac->action(SORT_BY_DESCRIPTION_ACTION_NAME)->setEnabled(m_ac->action(SORT_BY_NAME_ACTION_NAME)->isEnabled()); m_ac->action(MOVE_UP_ACTION_NAME)->setEnabled(selected && (parentItem->indexOfChild(_item) > 0)); m_ac->action(MOVE_DOWN_ACTION_NAME)->setEnabled(selected && (parentItem->indexOfChild(_item) < parentItem->childCount() - 1)); if (!item) { emit disableAction(); return; } if (_item->isDirectory()) { emit entrySelected(_item->folderInfo()); } else { emit entrySelected(_item->entryInfo()); } } void TreeView::currentDataChanged(MenuFolderInfo *folderInfo) { TreeItem *item = (TreeItem *)selectedItem(); if (!item || !folderInfo) { return; } item->setName(folderInfo->caption); item->setIcon(0, QIcon::fromTheme(folderInfo->icon)); } void TreeView::currentDataChanged(MenuEntryInfo *entryInfo) { TreeItem *item = (TreeItem *)selectedItem(); if (!item || !entryInfo) { return; } QString name; if (m_detailedMenuEntries && !entryInfo->description.isEmpty()) { if (m_detailedEntriesNamesFirst) { name = entryInfo->caption + QLatin1String(" (") + entryInfo->description + QLatin1Char(')'); } else { name = entryInfo->description + QLatin1String(" (") + entryInfo->caption + QLatin1Char(')'); } } else { name = entryInfo->caption; } item->setName(name); item->setIcon(0, QIcon::fromTheme(entryInfo->icon)); } Qt::DropActions TreeView::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList TreeView::mimeTypes() const { return m_dropMimeTypes; } void TreeView::startDrag(Qt::DropActions supportedActions) { QList items; items << selectedItem(); QMimeData *data = mimeData(items); if (!data) { return; } QDrag *drag = new QDrag(this); int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small); drag->setPixmap(selectedItem()->icon(0).pixmap(iconSize, iconSize)); drag->setMimeData(data); drag->exec(supportedActions, Qt::MoveAction); } QMimeData *TreeView::mimeData(const QList items) const { if (items.isEmpty()) { return nullptr; } return new MenuItemMimeData(dynamic_cast(items.first())); } static QString createDesktopFile(const QString &file, QString *menuId, QStringList *excludeList) { QString base = file.mid(file.lastIndexOf(QLatin1Char('/'))+1); base = base.left(base.lastIndexOf(QLatin1Char('.'))); QRegExp r(QStringLiteral("(.*)(?=-\\d+)")); base = (r.indexIn(base) > -1) ? r.cap(1) : base; QString result = KService::newServicePath(true, base, menuId, excludeList); excludeList->append(*menuId); // Todo for Undo-support: Undo menuId allocation: return result; } static KDesktopFile *copyDesktopFile(MenuEntryInfo *entryInfo, QString *menuId, QStringList *excludeList) { QString result = createDesktopFile(entryInfo->file(), menuId, excludeList); KDesktopFile *df = entryInfo->desktopFile()->copyTo(result); df->desktopGroup().deleteEntry("Categories"); // Don't set any categories! return df; } static QString createDirectoryFile(const QString &file, QStringList *excludeList) { QString base = file.mid(file.lastIndexOf(QLatin1Char('/'))+1); base = base.left(base.lastIndexOf(QLatin1Char('.'))); QString result; int i = 1; while (true) { if (i == 1) { result = base + QStringLiteral(".directory"); } else { result = base + QStringLiteral("-%1.directory").arg(i); } if (!excludeList->contains(result)) { - if (KStandardDirs::locate("xdgdata-dirs", result).isEmpty()) { + if (QStandardPaths::locate(QStandardPaths::GenericDataLocation, QLatin1String("desktop-directories/") + result).isEmpty()) { break; } } i++; } excludeList->append(result); - result = KStandardDirs::locateLocal("xdgdata-dirs", result); + result = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/desktop-directories/") + result; return result; } bool TreeView::dropMimeData(QTreeWidgetItem *item, int index, const QMimeData *data, Qt::DropAction action) { // get destination folder TreeItem *titem = item ? dynamic_cast(item) : nullptr; if (item && !titem) { return false; } TreeItem *parentItem = nullptr; QTreeWidgetItem *after = titem; // find the parent item and which item the dropped item should go after if (titem) { if (titem->isDirectory()) { parentItem = titem; after = titem->child(index); if (!after) { after = titem->child(titem->childCount() - 1); } } else { parentItem = dynamic_cast(titem->parent()); if (titem->parent() && !parentItem) { return false; } } } else if (index > 0) { after = topLevelItem(index); if (!after) { after = topLevelItem(topLevelItemCount() - 1); } } QString folder = parentItem ? parentItem->directory() : QStringLiteral("/"); MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder; //qCDebug(KMENUEDIT_LOG) << "think we're dropping on" << (parentItem ? parentItem->text(0) : "Top Level") << index; if (!data->hasFormat(QLatin1String(s_internalMimeType))) { // External drop if (!KUrl::List::canDecode(data)) { return false; } KUrl::List urls = KUrl::List::fromMimeData(data); if (urls.isEmpty() || !urls[0].isLocalFile()) { return false; } //FIXME: this should really support multiple DnD QString path = urls[0].path(); if (!path.endsWith(QLatin1String(".desktop"))) { return false; } QString menuId; QString result = createDesktopFile(path, &menuId, &m_newMenuIds); KDesktopFile orig_df(path); KDesktopFile *df = orig_df.copyTo(result); df->desktopGroup().deleteEntry("Categories"); // Don't set any categories! KService::Ptr s(new KService(df)); s->setMenuId(menuId); MenuEntryInfo *entryInfo = new MenuEntryInfo(s, df); QString oldCaption = entryInfo->caption; QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption); entryInfo->setCaption(newCaption); // Add file to menu // m_menuFile->addEntry(folder, menuId); m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId); // create the TreeItem if (parentItem) { parentItem->setExpanded(true); } // update fileInfo data parentFolderInfo->add(entryInfo); TreeItem *newItem = createTreeItem(parentItem, after, entryInfo, true); setCurrentItem(newItem); setLayoutDirty(parentItem); return true; } QVariant p(data->data(QLatin1String(s_internalMimeType))); const MenuItemMimeData *itemData = dynamic_cast(data); if (!itemData) { return false; } TreeItem *dragItem = itemData->item(); if (!dragItem || dragItem == after) { return false; // Nothing to do } ////qCDebug(KMENUEDIT_LOG) << "an internal drag of" << dragItem->text(0) << (parentItem ? parentItem->text(0) : "Top level"); if (dragItem->isDirectory()) { MenuFolderInfo *folderInfo = dragItem->folderInfo(); if (action == Qt::CopyAction) { // FIXME: // * Create new .directory file } else { TreeItem *tmpItem = static_cast(parentItem); while (tmpItem) { if (tmpItem == dragItem) { return false; } tmpItem = static_cast(tmpItem->parent()); } // Remove MenuFolderInfo TreeItem *oldParentItem = static_cast(dragItem->parent()); MenuFolderInfo *oldParentFolderInfo = oldParentItem ? oldParentItem->folderInfo() : m_rootFolder; oldParentFolderInfo->take(folderInfo); // Move menu QString oldFolder = folderInfo->fullId; QString folderName = folderInfo->id; QString newFolder = m_menuFile->uniqueMenuName(folder, folderName, parentFolderInfo->existingMenuIds()); folderInfo->id = newFolder; // Add file to menu //m_menuFile->moveMenu(oldFolder, folder + newFolder); //qCDebug(KMENUEDIT_LOG) << "moving" << dragItem->text(0) << "to" << folder + newFolder; m_menuFile->pushAction(MenuFile::MOVE_MENU, oldFolder, folder + newFolder); // Make sure caption is unique QString newCaption = parentFolderInfo->uniqueMenuCaption(folderInfo->caption); if (newCaption != folderInfo->caption) { folderInfo->setCaption(newCaption); } // create the TreeItem if (parentItem) { parentItem->setExpanded(true); } // update fileInfo data folderInfo->updateFullId(parentFolderInfo->fullId); folderInfo->setInUse(true); parentFolderInfo->add(folderInfo); if (parentItem != oldParentItem) { if (oldParentItem) { oldParentItem->takeChild(oldParentItem->indexOfChild(dragItem)); } else { takeTopLevelItem(indexOfTopLevelItem(dragItem)); } } if (parentItem) { parentItem->insertChild(after ? parentItem->indexOfChild(after) + 1 : parentItem->childCount(), dragItem); } else { insertTopLevelItem(after ? indexOfTopLevelItem(after) : topLevelItemCount(), dragItem); } dragItem->setName(folderInfo->caption); dragItem->setDirectoryPath(folderInfo->fullId); setCurrentItem(dragItem); } } else if (dragItem->isEntry()) { MenuEntryInfo *entryInfo = dragItem->entryInfo(); QString menuId = entryInfo->menuId(); if (action == Qt::CopyAction) { // Need to copy file and then add it KDesktopFile *df = copyDesktopFile(entryInfo, &menuId, &m_newMenuIds); // Duplicate //UNDO-ACTION: NEW_MENU_ID (menuId) KService::Ptr s(new KService(df)); s->setMenuId(menuId); entryInfo = new MenuEntryInfo(s, df); QString oldCaption = entryInfo->caption; QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption); entryInfo->setCaption(newCaption); } else { del(dragItem, false); QString oldCaption = entryInfo->caption; QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption); entryInfo->setCaption(newCaption); entryInfo->setInUse(true); } // Add file to menu // m_menuFile->addEntry(folder, menuId); m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId); // create the TreeItem if (parentItem) { parentItem->setExpanded(true); } // update fileInfo data parentFolderInfo->add(entryInfo); TreeItem *newItem = createTreeItem(parentItem, after, entryInfo); setCurrentItem(newItem); } else { // copying a separator if (action != Qt::CopyAction) { del(dragItem, false); } TreeItem *newItem = createTreeItem(parentItem, after, m_separator); setCurrentItem(newItem); } //qCDebug(KMENUEDIT_LOG) << "setting the layout to be dirty at" << parentItem; setLayoutDirty(parentItem); return true; } QTreeWidgetItem *TreeView::selectedItem() { QList selection = selectedItems(); if (selection.isEmpty()) { return nullptr; } return selection.first(); } void TreeView::contextMenuEvent(QContextMenuEvent *event) { if (m_popupMenu && itemAt(event->pos())) { m_popupMenu->exec(event->globalPos()); } } void TreeView::dropEvent(QDropEvent *event) { // this prevents QTreeWidget from interfering with our moves QTreeView::dropEvent(event); } void TreeView::newsubmenu() { TreeItem *parentItem = nullptr; TreeItem *item = (TreeItem *)selectedItem(); bool ok; QString caption = KInputDialog::getText(i18n("New Submenu"), i18n("Submenu name:"), QString(), &ok, this); if (!ok) { return; } QString file = caption; file.replace(QLatin1Char('/'), QLatin1Char('-')); file = createDirectoryFile(file, &m_newDirectoryList); // Create // get destination folder QString folder; if (!item) { parentItem = nullptr; folder.clear(); } else if (item->isDirectory()) { parentItem = item; item = nullptr; folder = parentItem->directory(); } else { parentItem = static_cast(item->parent()); folder = parentItem ? parentItem->directory() : QString(); } MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder; MenuFolderInfo *folderInfo = new MenuFolderInfo(); folderInfo->caption = parentFolderInfo->uniqueMenuCaption(caption); folderInfo->id = m_menuFile->uniqueMenuName(folder, caption, parentFolderInfo->existingMenuIds()); folderInfo->directoryFile = file; folderInfo->icon = QLatin1String("package"); folderInfo->hidden = false; folderInfo->setDirty(); KDesktopFile *df = new KDesktopFile(file); KConfigGroup desktopGroup = df->desktopGroup(); desktopGroup.writeEntry("Name", folderInfo->caption); desktopGroup.writeEntry("Icon", folderInfo->icon); df->sync(); delete df; // Add file to menu // m_menuFile->addMenu(folder + folderInfo->id, file); m_menuFile->pushAction(MenuFile::ADD_MENU, folder + folderInfo->id, file); folderInfo->fullId = parentFolderInfo->fullId + folderInfo->id; // create the TreeItem if (parentItem) { parentItem->setExpanded(true); } // update fileInfo data parentFolderInfo->add(folderInfo); TreeItem *newItem = createTreeItem(parentItem, item, folderInfo, true); setCurrentItem(newItem); setLayoutDirty(parentItem); } void TreeView::newitem() { TreeItem *parentItem = nullptr; TreeItem *item = (TreeItem *)selectedItem(); bool ok; QString caption = KInputDialog::getText(i18n("New Item"), i18n("Item name:"), QString(), &ok, this); if (!ok) { return; } QString menuId; QString file = caption; file.replace(QLatin1Char('/'), QLatin1Char('-')); file = createDesktopFile(file, &menuId, &m_newMenuIds); // Create KDesktopFile *df = new KDesktopFile(file); KConfigGroup desktopGroup = df->desktopGroup(); desktopGroup.writeEntry("Name", caption); desktopGroup.writeEntry("Type", "Application"); // get destination folder QString folder; if (!item) { parentItem = nullptr; folder.clear(); } else if (item->isDirectory()) { parentItem = item; item = nullptr; folder = parentItem->directory(); } else { parentItem = static_cast(item->parent()); folder = parentItem ? parentItem->directory() : QString(); } MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder; // Add file to menu // m_menuFile->addEntry(folder, menuId); m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId); KService::Ptr s(new KService(df)); s->setMenuId(menuId); MenuEntryInfo *entryInfo = new MenuEntryInfo(s, df); // create the TreeItem if (parentItem) { parentItem->setExpanded(true); } // update fileInfo data parentFolderInfo->add(entryInfo); TreeItem *newItem = createTreeItem(parentItem, item, entryInfo, true); setCurrentItem(newItem); setLayoutDirty(parentItem); } void TreeView::newsep() { TreeItem *parentItem = nullptr; TreeItem *item = (TreeItem *)selectedItem(); if (!item) { parentItem = nullptr; } else if (item->isDirectory()) { parentItem = item; item = nullptr; } else { parentItem = static_cast(item->parent()); } // create the TreeItem if (parentItem) { parentItem->setExpanded(true); } TreeItem *newItem = createTreeItem(parentItem, item, m_separator, true); setCurrentItem(newItem); setLayoutDirty(parentItem); } void TreeView::cut() { copy(true); // Select new current item // TODO: is this completely redundant? setCurrentItem(currentItem()); } void TreeView::copy() { copy(false); } void TreeView::copy(bool cutting) { TreeItem *item = (TreeItem *)selectedItem(); // nil selected? -> nil to copy if (item == nullptr) { return; } if (cutting) { setLayoutDirty((TreeItem *)item->parent()); } // clean up old stuff cleanupClipboard(); // is item a folder or a file? if (item->isDirectory()) { QString folder = item->directory(); if (cutting) { // Place in clipboard m_clipboard = MOVE_FOLDER; m_clipboardFolderInfo = item->folderInfo(); del(item, false); } else { // Place in clipboard m_clipboard = COPY_FOLDER; m_clipboardFolderInfo = item->folderInfo(); } } else if (item->isEntry()) { if (cutting) { // Place in clipboard m_clipboard = MOVE_FILE; m_clipboardEntryInfo = item->entryInfo(); del(item, false); } else { // Place in clipboard m_clipboard = COPY_FILE; m_clipboardEntryInfo = item->entryInfo(); } } else { // Place in clipboard m_clipboard = COPY_SEPARATOR; if (cutting) { del(item, false); } } m_ac->action(PASTE_ACTION_NAME)->setEnabled(true); } void TreeView::paste() { TreeItem *parentItem = nullptr; TreeItem *item = (TreeItem *)selectedItem(); // nil selected? -> nil to paste to if (item == nullptr) { return; } // is there content in the clipboard? if (!m_clipboard) { return; } // get destination folder QString folder; if (item->isDirectory()) { parentItem = item; item = nullptr; folder = parentItem->directory(); } else { parentItem = static_cast(item->parent()); folder = parentItem ? parentItem->directory() : QString(); } MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder; int command = m_clipboard; if ((command == COPY_FOLDER) || (command == MOVE_FOLDER)) { MenuFolderInfo *folderInfo = m_clipboardFolderInfo; if (command == COPY_FOLDER) { // Ugh.. this is hard :) // * Create new .directory file // Add } else if (command == MOVE_FOLDER) { // Move menu QString oldFolder = folderInfo->fullId; QString folderName = folderInfo->id; QString newFolder = m_menuFile->uniqueMenuName(folder, folderName, parentFolderInfo->existingMenuIds()); folderInfo->id = newFolder; // Add file to menu // m_menuFile->moveMenu(oldFolder, folder + newFolder); m_menuFile->pushAction(MenuFile::MOVE_MENU, oldFolder, folder + newFolder); // Make sure caption is unique QString newCaption = parentFolderInfo->uniqueMenuCaption(folderInfo->caption); if (newCaption != folderInfo->caption) { folderInfo->setCaption(newCaption); } // create the TreeItem if (parentItem) { parentItem->setExpanded(true); } // update fileInfo data folderInfo->fullId = parentFolderInfo->fullId + folderInfo->id; folderInfo->setInUse(true); parentFolderInfo->add(folderInfo); TreeItem *newItem = createTreeItem(parentItem, item, folderInfo); setCurrentItem(newItem); } m_clipboard = COPY_FOLDER; // Next one copies. } else if ((command == COPY_FILE) || (command == MOVE_FILE)) { MenuEntryInfo *entryInfo = m_clipboardEntryInfo; QString menuId; if (command == COPY_FILE) { // Need to copy file and then add it KDesktopFile *df = copyDesktopFile(entryInfo, &menuId, &m_newMenuIds); // Duplicate KService::Ptr s(new KService(df)); s->setMenuId(menuId); entryInfo = new MenuEntryInfo(s, df); QString oldCaption = entryInfo->caption; QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption); entryInfo->setCaption(newCaption); } else if (command == MOVE_FILE) { menuId = entryInfo->menuId(); m_clipboard = COPY_FILE; // Next one copies. QString oldCaption = entryInfo->caption; QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption); entryInfo->setCaption(newCaption); entryInfo->setInUse(true); } // Add file to menu // m_menuFile->addEntry(folder, menuId); m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId); // create the TreeItem if (parentItem) { parentItem->setExpanded(true); } // update fileInfo data parentFolderInfo->add(entryInfo); TreeItem *newItem = createTreeItem(parentItem, item, entryInfo, true); setCurrentItem(newItem); } else { // create separator if (parentItem) { parentItem->setExpanded(true); } TreeItem *newItem = createTreeItem(parentItem, item, m_separator, true); setCurrentItem(newItem); } setLayoutDirty(parentItem); } /** * This slot is called from the signal mapper to sort children contained in an item. * This item is determinated according to the chosen sort type. * * @brief Determine which item is to sort, and do it. * @param sortCmd Sort type. */ void TreeView::sort(const int sortCmd) { // determine the chosen sort type and the selected item SortType sortType = static_cast(sortCmd); TreeItem *itemToSort; if (sortType == SortByName || sortType == SortByDescription) { itemToSort = static_cast(selectedItem()); } else if (sortType == SortAllByDescription) { sortType = SortByDescription; itemToSort = static_cast(invisibleRootItem()); } else { /* if (sortType == SortAllByName) */ sortType = SortByName; itemToSort = static_cast(invisibleRootItem()); } // proceed to the sorting sortItem(itemToSort, sortType); } /** * Sort children of the given item, according to the sort type. * The sorting is done on children groups, splited by separator items. * * @brief Sort item children. * @param item Item to sort. * @param sortType Sort type. */ void TreeView::sortItem(TreeItem *item, const SortType &sortType) { // sort the selected item only if contains children if ((!item->isDirectory()) || (item->childCount() == 0)) { return; } // remove contained children QList children = item->takeChildren(); // sort children groups, splited by separator items QList::iterator startIt = children.begin(); QList::iterator currentIt = children.begin(); while (currentIt != children.end()) { TreeItem *child = static_cast(*currentIt); // if it's a separator, sort previous items and continue on following items if (child->isSeparator() && startIt != currentIt) { sortItemChildren(startIt, currentIt, sortType); startIt = currentIt + 1; } ++currentIt; } sortItemChildren(startIt, currentIt, sortType); // insert sorted children in the tree item->addChildren(children); foreach (QTreeWidgetItem *child, children) { // recreate item widget for separators TreeItem *treeItem = static_cast(child); if (treeItem->isSeparator()) { setItemWidget(treeItem, 0, new SeparatorWidget); } // try to sort sub-children sortItem(static_cast(child), sortType); } // flag current item as dirty TreeItem *itemToFlagAsDirty = item; // if tree root item, set the entire layout as dirty if (item == invisibleRootItem()) { itemToFlagAsDirty = nullptr; } setLayoutDirty(itemToFlagAsDirty); } /** * Sort a children range defined with two list iterators, according to the sort type. * * @brief Sort a children range. * @param begin First child iterator. * @param end Last child iterator (exclusive, pointed child won't be affected). * @param sortType Sort type. */ void TreeView::sortItemChildren(const QList::iterator &begin, const QList::iterator &end, const SortType &sortType) { // sort by name if (sortType == SortByName) { std::sort(begin, end, TreeItem::itemNameLessThan); } // sort by description else if (sortType == SortByDescription) { std::sort(begin, end, TreeItem::itemDescriptionLessThan); } } /** * @brief Move up the selected item. */ void TreeView::moveUpItem() { moveUpOrDownItem(true); } /** * @brief Move down the selected item. */ void TreeView::moveDownItem() { moveUpOrDownItem(false); } /** * Move the selected item on desired direction (up or down). * * @brief Move up/down the selected item. * @param isMovingUpAction True to move up, false to move down. */ void TreeView::moveUpOrDownItem(bool isMovingUpAction) { // get the selected item and its parent TreeItem *sourceItem = static_cast(selectedItem()); if (!sourceItem) { return; } TreeItem *parentItem = getParentItem(sourceItem); // get selected item index int sourceItemIndex = parentItem->indexOfChild(sourceItem); // find the second item to swap TreeItem *destItem = nullptr; int destIndex; if (isMovingUpAction) { destIndex = sourceItemIndex - 1; destItem = static_cast(parentItem->child(destIndex)); } else { destIndex = sourceItemIndex + 1; destItem = static_cast(parentItem->child(destIndex)); } // swap items parentItem->removeChild(sourceItem); parentItem->insertChild(destIndex, sourceItem); // recreate item widget for separators if (sourceItem->isSeparator()) { setItemWidget(sourceItem, 0, new SeparatorWidget); } if (destItem->isSeparator()) { setItemWidget(destItem, 0, new SeparatorWidget); } // set the focus on the source item setCurrentItem(sourceItem); // flag parent item as dirty (if the parent is the root item, set the entire layout as dirty) if (parentItem == invisibleRootItem()) { parentItem = nullptr; } setLayoutDirty(parentItem); } /** * For a given item, return its parent. For top items, return the invisible root item. * * @brief Get the parent item. * @param item Item. * @return Parent item. */ TreeItem *TreeView::getParentItem(QTreeWidgetItem *item) const { QTreeWidgetItem *parentItem = item->parent(); if (!parentItem) { parentItem = invisibleRootItem(); } return static_cast(parentItem); } void TreeView::del() { TreeItem *item = (TreeItem *)selectedItem(); // nil selected? -> nil to delete if (item == nullptr) { return; } del(item, true); // Select new current item // TODO: is this completely redundant? setCurrentItem(currentItem()); } void TreeView::del(TreeItem *item, bool deleteInfo) { TreeItem *parentItem = static_cast(item->parent()); // is file a .directory or a .desktop file if (item->isDirectory()) { if (KMessageBox::warningYesNo(this, i18n("All submenus of '%1' will be removed. Do you want to continue?", item->name())) == KMessageBox::No) { return; } MenuFolderInfo *folderInfo = item->folderInfo(); // Remove MenuFolderInfo MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder; parentFolderInfo->take(folderInfo); folderInfo->setInUse(false); if (m_clipboard == COPY_FOLDER && (m_clipboardFolderInfo == folderInfo)) { // Copy + Del == Cut m_clipboard = MOVE_FOLDER; // Clipboard now owns folderInfo } else { if (folderInfo->takeRecursive(m_clipboardFolderInfo)) { m_clipboard = MOVE_FOLDER; // Clipboard now owns m_clipboardFolderInfo } if (deleteInfo) { delete folderInfo; // Delete folderInfo } } // Remove from menu // m_menuFile->removeMenu(item->directory()); m_menuFile->pushAction(MenuFile::REMOVE_MENU, item->directory(), QString()); // Remove tree item delete item; } else if (item->isEntry()) { MenuEntryInfo *entryInfo = item->entryInfo(); QString menuId = entryInfo->menuId(); // Remove MenuFolderInfo MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder; parentFolderInfo->take(entryInfo); entryInfo->setInUse(false); if (m_clipboard == COPY_FILE && (m_clipboardEntryInfo == entryInfo)) { // Copy + Del == Cut m_clipboard = MOVE_FILE; // Clipboard now owns entryInfo } else { if (deleteInfo) { delete entryInfo; // Delete entryInfo } } // Remove from menu QString folder = parentItem ? parentItem->directory() : QString(); // m_menuFile->removeEntry(folder, menuId); m_menuFile->pushAction(MenuFile::REMOVE_ENTRY, folder, menuId); // Remove tree item delete item; } else { // Remove separator delete item; } setLayoutDirty(parentItem); } void TreeView::cleanupClipboard() { if (m_clipboard == MOVE_FOLDER) { delete m_clipboardFolderInfo; } m_clipboardFolderInfo = nullptr; if (m_clipboard == MOVE_FILE) { delete m_clipboardEntryInfo; } m_clipboardEntryInfo = nullptr; m_clipboard = 0; } static QStringList extractLayout(QTreeWidget *tree, QTreeWidgetItem *parent) { QStringList layout; if (!parent && !tree) { return layout; } bool firstFolder = true; bool firstEntry = true; int max = parent ? parent->childCount() : tree->topLevelItemCount(); for (int i = 0; i < max; ++i) { TreeItem *item = dynamic_cast(parent ? parent->child(i) : tree->topLevelItem(i)); if (!item) { continue; } if (item->isDirectory()) { if (firstFolder) { firstFolder = false; layout << QStringLiteral(":M"); // Add new folders here... } layout << (item->folderInfo()->id); } else if (item->isEntry()) { if (firstEntry) { firstEntry = false; layout << QStringLiteral(":F"); // Add new entries here... } layout << (item->entryInfo()->menuId()); } else { layout << QStringLiteral(":S"); } } return layout; } void TreeItem::saveLayout(MenuFile *menuFile) { if (m_layoutDirty) { QStringList layout = extractLayout(nullptr, this); menuFile->setLayout(folderInfo()->fullId, layout); m_layoutDirty = false; } for (int i = 0; i < childCount(); ++i) { TreeItem *item = dynamic_cast(child(i)); if (item) { item->saveLayout(menuFile); } } } void TreeView::saveLayout() { if (m_layoutDirty) { QStringList layout = extractLayout(this, nullptr); m_menuFile->setLayout(m_rootFolder->fullId, layout); m_layoutDirty = false; } for (int i = 0; i < topLevelItemCount(); ++i) { TreeItem *item = dynamic_cast(topLevelItem(i)); if (item) { item->saveLayout(m_menuFile); } } } bool TreeView::save() { saveLayout(); m_rootFolder->save(m_menuFile); bool success = m_menuFile->performAllActions(); m_newMenuIds.clear(); m_newDirectoryList.clear(); if (success) { KBuildSycocaProgressDialog::rebuildKSycoca(this); } else { KMessageBox::sorry(this, QStringLiteral("")+i18n("Menu changes could not be saved because of the following problem:")+QStringLiteral("

") +m_menuFile->error()+QStringLiteral("
")); } sendReloadMenu(); return success; } void TreeView::setLayoutDirty(TreeItem *parentItem) { if (parentItem) { parentItem->setLayoutDirty(); } else { m_layoutDirty = true; } } bool TreeView::isLayoutDirty() { for (int i = 0; i < topLevelItemCount(); ++i) { TreeItem *item = dynamic_cast(topLevelItem(i)); if (!item) { continue; } if (item->isLayoutDirty()) { return true; } } return false; } bool TreeView::dirty() { return m_layoutDirty || m_rootFolder->hasDirt() || m_menuFile->dirty() || isLayoutDirty(); } void TreeView::findServiceShortcut(const QKeySequence &cut, KService::Ptr &service) { service = m_rootFolder->findServiceShortcut(cut); } void TreeView::restoreMenuSystem() { if (KMessageBox::warningYesNo(this, i18n("Do you want to restore the system menu? Warning: This will remove all custom menus.")) == KMessageBox::No) { return; } - QString kmenueditfile = KStandardDirs::locateLocal("xdgconf-menu", QStringLiteral("applications-kmenuedit.menu")); + const QString kmenueditfile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QLatin1String("/menus/applications-kmenuedit.menu"); if (QFile::exists(kmenueditfile)) { if (!QFile::remove(kmenueditfile)) { qCWarning(KMENUEDIT_LOG)<<"Could not delete "<KStandardDirs::localxdgdatadir(); - if (!KIO::NetAccess::del(QUrl::fromLocalFile(xdgdir + QStringLiteral("/applications")), this)) { - qCWarning(KMENUEDIT_LOG)<<"Could not delete dir :"<<(xdgdir+ QStringLiteral("/applications")); + const QString xdgappsdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/applications"); + if (QFileInfo(xdgappsdir).isDir()) { + if (!QDir(xdgappsdir).removeRecursively()) { + qCWarning(KMENUEDIT_LOG)<<"Could not delete dir :" << xdgappsdir; + } } - if (!KIO::NetAccess::del(QUrl::fromLocalFile(xdgdir + QStringLiteral("/desktop-directories")), this)) { - qCWarning(KMENUEDIT_LOG)<<"Could not delete dir :"<<(xdgdir + QStringLiteral("/desktop-directories")); + const QString xdgdesktopdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/desktop-directories"); + if (QFileInfo(xdgdesktopdir).isDir()) { + if (!QDir(xdgdesktopdir).removeRecursively()) { + qCWarning(KMENUEDIT_LOG)<<"Could not delete dir :" << xdgdesktopdir; + } } KBuildSycocaProgressDialog::rebuildKSycoca(this); clear(); cleanupClipboard(); delete m_rootFolder; delete m_separator; m_layoutDirty = false; m_newMenuIds.clear(); m_newDirectoryList.clear(); m_menuFile->restoreMenuSystem(kmenueditfile); m_rootFolder = new MenuFolderInfo; m_separator = new MenuSeparatorInfo; readMenuFolderInfo(); fill(); sendReloadMenu(); emit disableAction(); emit entrySelected((MenuEntryInfo *)nullptr); } void TreeView::updateTreeView(bool showHidden) { m_showHidden = showHidden; clear(); cleanupClipboard(); delete m_rootFolder; delete m_separator; m_layoutDirty = false; m_newMenuIds.clear(); m_newDirectoryList.clear(); m_rootFolder = new MenuFolderInfo; m_separator = new MenuSeparatorInfo; readMenuFolderInfo(); fill(); sendReloadMenu(); emit disableAction(); emit entrySelected((MenuEntryInfo *)nullptr); } void TreeView::sendReloadMenu() { QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/kickoff"), QStringLiteral("org.kde.plasma"), QStringLiteral("reloadMenu")); QDBusConnection::sessionBus().send(message); } // Slot to expand or retract items in tree based on length of search string void TreeView::searchUpdated(const QString &searchString) { // expand all categories if we typed more than a few characters, otherwise collapse and un-select everything // use logicalLength for CJK users if (KStringHandler::logicalLength(searchString) > 2) { expandAll(); } else { collapseAll(); setCurrentIndex(rootIndex()); } } MenuItemMimeData::MenuItemMimeData(TreeItem *item) : QMimeData() , m_item(item) { } TreeItem *MenuItemMimeData::item() const { return m_item; } QStringList MenuItemMimeData::formats() const { QStringList formats; if (!m_item) { return formats; } formats << QLatin1String(s_internalMimeType); return formats; } bool MenuItemMimeData::hasFormat(const QString &mimeType) const { return m_item && mimeType == QLatin1String(s_internalMimeType); } QVariant MenuItemMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const { Q_UNUSED(type); if (m_item && mimeType == QLatin1String(s_internalMimeType)) { return qVariantFromValue(m_item); } return QVariant(); }