File indexing completed on 2024-04-14 12:33:13

0001 /*
0002  *   SPDX-FileCopyrightText: 2000 Matthias Elter <elter@kde.org>
0003  *   SPDX-FileCopyrightText: 2001-2002 Raffaele Sandrini <sandrini@kde.org>
0004  *   SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
0005  *   SPDX-FileCopyrightText: 2008 Laurent Montel <montel@kde.org>
0006  *
0007  *   SPDX-License-Identifier: GPL-2.0-or-later
0008  *
0009  */
0010 
0011 #include "treeview.h"
0012 
0013 #include <unistd.h>
0014 
0015 #include <QAction>
0016 #include <QApplication>
0017 #include <QDBusConnection>
0018 #include <QDBusMessage>
0019 #include <QDir>
0020 #include <QDrag>
0021 #include <QDropEvent>
0022 #include <QHeaderView>
0023 #include <QIcon>
0024 #include <QInputDialog>
0025 #include <QMenu>
0026 #include <QPainter>
0027 #include <QRegularExpressionMatch>
0028 #include <QUrl>
0029 
0030 #include "kmenuedit_debug.h"
0031 #include <KActionCollection>
0032 #include <KBuildSycocaProgressDialog>
0033 #include <KConfig>
0034 #include <KConfigGroup>
0035 #include <KDesktopFile>
0036 #include <KIconLoader>
0037 #include <KLocalizedString>
0038 #include <KMessageBox>
0039 #include <KStringHandler>
0040 #include <KUrlMimeData>
0041 #include <QStandardPaths>
0042 
0043 #include "menufile.h"
0044 #include "menuinfo.h"
0045 
0046 #define MOVE_FOLDER 'M'
0047 #define COPY_FOLDER 'C'
0048 #define MOVE_FILE 'm'
0049 #define COPY_FILE 'c'
0050 #define COPY_SEPARATOR 'S'
0051 
0052 static const char *s_internalMimeType = "application/x-kmenuedit-internal";
0053 
0054 class SeparatorWidget : public QWidget
0055 {
0056 public:
0057     SeparatorWidget()
0058         : QWidget(nullptr)
0059     {
0060     }
0061 
0062 protected:
0063     void paintEvent(QPaintEvent * /*event*/) override
0064     {
0065         QPainter p(this);
0066         // Draw Separator
0067         int h = (height() / 2) - 1;
0068         //        if (isSelected()) {
0069         //            p->setPen( palette().color( QPalette::HighlightedText ) );
0070         //        } else {
0071         //            p->setPen( palette().color( QPalette::Text ) );
0072         //        }
0073 
0074         p.drawLine(2, h, width() - 4, h);
0075     }
0076 };
0077 
0078 TreeItem::TreeItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, const QString &menuId, bool _m_init)
0079     : QTreeWidgetItem(parent, after)
0080     , m_hidden(false)
0081     , m_init(_m_init)
0082     , m_layoutDirty(false)
0083     , m_menuId(menuId)
0084     , m_folderInfo(nullptr)
0085     , m_entryInfo(nullptr)
0086 {
0087 }
0088 
0089 TreeItem::TreeItem(QTreeWidget *parent, QTreeWidgetItem *after, const QString &menuId, bool _m_init)
0090     : QTreeWidgetItem(parent, after)
0091     , m_hidden(false)
0092     , m_init(_m_init)
0093     , m_layoutDirty(false)
0094     , m_menuId(menuId)
0095     , m_folderInfo(nullptr)
0096     , m_entryInfo(nullptr)
0097 {
0098     load();
0099 }
0100 
0101 TreeItem::~TreeItem()
0102 {
0103 }
0104 
0105 /**
0106  * @brief Return the description.
0107  * @return Description, or an empty string if none.
0108  */
0109 QString TreeItem::description() const
0110 {
0111     QString description;
0112     if (isEntry()) {
0113         description = entryInfo()->description;
0114     }
0115     return description;
0116 }
0117 
0118 /**
0119  * @brief Compare two items using their names.
0120  * @param item1 First item.
0121  * @param item2 Second item.
0122  * @return Integer less than, equal to, or greater than zero if item1 is less than, equal to, or greater than item2.
0123  */
0124 bool TreeItem::itemNameLessThan(QTreeWidgetItem *item1, QTreeWidgetItem *item2)
0125 {
0126     TreeItem *treeItem1 = static_cast<TreeItem *>(item1);
0127     TreeItem *treeItem2 = static_cast<TreeItem *>(item2);
0128     return QString::localeAwareCompare(treeItem1->name(), treeItem2->name()) < 0;
0129 }
0130 
0131 /**
0132  * @brief Compare two items using their descriptions. If both are empty, sort them by name.
0133  * @param item1 First item.
0134  * @param item2 Second item.
0135  * @return Integer less than, equal to, or greater than zero if item1 is less than, equal to, or greater than item2.
0136  */
0137 bool TreeItem::itemDescriptionLessThan(QTreeWidgetItem *item1, QTreeWidgetItem *item2)
0138 {
0139     // extract descriptions in lower case
0140     TreeItem *treeItem1 = static_cast<TreeItem *>(item1);
0141     TreeItem *treeItem2 = static_cast<TreeItem *>(item2);
0142     const QString description1 = treeItem1->description();
0143     const QString description2 = treeItem2->description();
0144 
0145     // if description is missing for both items, sort them using their names
0146     if (description1.isEmpty() && description2.isEmpty()) {
0147         return itemNameLessThan(item1, item2);
0148     } else {
0149         return QString::localeAwareCompare(description1, description2) < 0;
0150     }
0151 }
0152 
0153 void TreeItem::setName(const QString &name)
0154 {
0155     if (m_name == name) {
0156         return;
0157     }
0158 
0159     m_name = name;
0160     update();
0161 }
0162 
0163 void TreeItem::setHiddenInMenu(bool b)
0164 {
0165     if (m_hidden == b) {
0166         return;
0167     }
0168 
0169     m_hidden = b;
0170     update();
0171 }
0172 
0173 void TreeItem::update()
0174 {
0175     QString s = m_name;
0176     if (m_hidden) {
0177         s += i18n(" [Hidden]");
0178     }
0179 
0180     setText(0, s);
0181 }
0182 
0183 void TreeItem::load()
0184 {
0185     if (m_folderInfo && !m_init) {
0186         m_init = true;
0187         TreeView *tv = static_cast<TreeView *>(treeWidget());
0188         tv->fillBranch(m_folderInfo, this);
0189     }
0190 }
0191 
0192 bool TreeItem::isLayoutDirty() const
0193 {
0194     if (m_layoutDirty) {
0195         return true;
0196     }
0197 
0198     for (int i = 0; i < childCount(); ++i) {
0199         TreeItem *item = dynamic_cast<TreeItem *>(child(i));
0200         if (!item) {
0201             continue;
0202         }
0203 
0204         if (item->isLayoutDirty()) {
0205             return true;
0206         }
0207     }
0208 
0209     return false;
0210 }
0211 
0212 TreeView::TreeView(KActionCollection *ac, QWidget *parent)
0213     : QTreeWidget(parent)
0214     , m_ac(ac)
0215     , m_popupMenu(nullptr)
0216     , m_clipboard(0)
0217     , m_clipboardFolderInfo(nullptr)
0218     , m_clipboardEntryInfo(nullptr)
0219     , m_layoutDirty(false)
0220     , m_detailedMenuEntries(true)
0221     , m_detailedEntriesNamesFirst(true)
0222 {
0223     m_dropMimeTypes << QLatin1String(s_internalMimeType) << KUrlMimeData::mimeDataTypes();
0224     qRegisterMetaType<TreeItem *>("TreeItem");
0225     setAllColumnsShowFocus(true);
0226     setRootIsDecorated(true);
0227     setSortingEnabled(false);
0228     setDragEnabled(true);
0229     setAcceptDrops(true);
0230     setMinimumWidth(240);
0231 
0232     setHeaderLabels(QStringList() << QLatin1String(""));
0233     header()->hide();
0234 
0235     // listen for creation
0236     connect(m_ac->action(NEW_ITEM_ACTION_NAME), &QAction::triggered, this, &TreeView::newitem);
0237     connect(m_ac->action(NEW_SUBMENU_ACTION_NAME), &QAction::triggered, this, &TreeView::newsubmenu);
0238     connect(m_ac->action(NEW_SEPARATOR_ACTION_NAME), &QAction::triggered, this, &TreeView::newsep);
0239 
0240     // listen for copy
0241     connect(m_ac->action(CUT_ACTION_NAME), &QAction::triggered, this, &TreeView::cut);
0242     connect(m_ac->action(COPY_ACTION_NAME), SIGNAL(triggered()), SLOT(copy()));
0243     connect(m_ac->action(PASTE_ACTION_NAME), &QAction::triggered, this, &TreeView::paste);
0244 
0245     // listen for deleting
0246     connect(m_ac->action(DELETE_ACTION_NAME), SIGNAL(triggered()), SLOT(del()));
0247 
0248     // listen for sorting
0249     QAction *action = m_ac->action(SORT_BY_NAME_ACTION_NAME);
0250     connect(action, &QAction::triggered, this, [this]() {
0251         sort(SortByName);
0252     });
0253     action = m_ac->action(SORT_BY_DESCRIPTION_ACTION_NAME);
0254     connect(action, &QAction::triggered, this, [this]() {
0255         sort(SortByDescription);
0256     });
0257     action = m_ac->action(SORT_ALL_BY_NAME_ACTION_NAME);
0258     connect(action, &QAction::triggered, this, [this]() {
0259         sort(SortAllByName);
0260     });
0261     action = m_ac->action(SORT_ALL_BY_DESCRIPTION_ACTION_NAME);
0262     connect(action, &QAction::triggered, this, [this]() {
0263         sort(SortAllByDescription);
0264     });
0265 
0266     // connect moving up/down actions
0267     connect(m_ac->action(MOVE_UP_ACTION_NAME), &QAction::triggered, this, &TreeView::moveUpItem);
0268     connect(m_ac->action(MOVE_DOWN_ACTION_NAME), &QAction::triggered, this, &TreeView::moveDownItem);
0269 
0270     // listen for selection
0271     connect(this, &QTreeWidget::currentItemChanged, this, &TreeView::itemSelected);
0272 
0273     m_menuFile = new MenuFile(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/menus/") +
0274                               QStringLiteral("applications-kmenuedit.menu"));
0275     m_rootFolder = new MenuFolderInfo;
0276     m_separator = new MenuSeparatorInfo;
0277 }
0278 
0279 TreeView::~TreeView()
0280 {
0281     cleanupClipboard();
0282     delete m_rootFolder;
0283     delete m_separator;
0284     delete m_menuFile;
0285 }
0286 
0287 void TreeView::setViewMode(bool showHidden)
0288 {
0289     // setup popup menu
0290     delete m_popupMenu;
0291     m_popupMenu = new QMenu(this);
0292 
0293     // creation
0294     m_popupMenu->addAction(m_ac->action(NEW_ITEM_ACTION_NAME));
0295     m_popupMenu->addAction(m_ac->action(NEW_SUBMENU_ACTION_NAME));
0296     m_popupMenu->addAction(m_ac->action(NEW_SEPARATOR_ACTION_NAME));
0297     m_popupMenu->addSeparator();
0298 
0299     // copy
0300     m_popupMenu->addAction(m_ac->action(CUT_ACTION_NAME));
0301     m_popupMenu->addAction(m_ac->action(COPY_ACTION_NAME));
0302     m_popupMenu->addAction(m_ac->action(PASTE_ACTION_NAME));
0303     m_popupMenu->addSeparator();
0304 
0305     // delete
0306     m_popupMenu->addAction(m_ac->action(DELETE_ACTION_NAME));
0307     m_popupMenu->addSeparator();
0308 
0309     // move
0310     m_popupMenu->addAction(m_ac->action(MOVE_UP_ACTION_NAME));
0311     m_popupMenu->addAction(m_ac->action(MOVE_DOWN_ACTION_NAME));
0312     m_popupMenu->addSeparator();
0313 
0314     // sort
0315     m_popupMenu->addAction(m_ac->action(SORT_ACTION_NAME));
0316 
0317     m_showHidden = showHidden;
0318     readMenuFolderInfo();
0319     fill();
0320 }
0321 
0322 void TreeView::readMenuFolderInfo(MenuFolderInfo *folderInfo, KServiceGroup::Ptr folder, const QString &prefix)
0323 {
0324     if (!folderInfo) {
0325         folderInfo = m_rootFolder;
0326         folder = KServiceGroup::root();
0327     }
0328 
0329     if (!folder || !folder->isValid()) {
0330         return;
0331     }
0332 
0333     folderInfo->caption = folder->caption();
0334     folderInfo->comment = folder->comment();
0335 
0336     // Item names may contain ampersands. To avoid them being converted
0337     // to accelerators, replace them with two ampersands.
0338     folderInfo->hidden = folder->noDisplay();
0339     folderInfo->directoryFile = folder->directoryEntryPath();
0340     folderInfo->icon = folder->icon();
0341     QString id = folder->relPath();
0342     int i = id.lastIndexOf(QLatin1Char('/'), -2);
0343     id = id.mid(i + 1);
0344     folderInfo->id = id;
0345     folderInfo->fullId = prefix + id;
0346 
0347     for (const KSycocaEntry::Ptr &e : folder->entries(true, !m_showHidden, true, m_detailedMenuEntries && !m_detailedEntriesNamesFirst)) {
0348         if (e->isType(KST_KServiceGroup)) {
0349             const KServiceGroup::Ptr serviceGroup(static_cast<KServiceGroup *>(e.data()));
0350             MenuFolderInfo *subFolderInfo = new MenuFolderInfo();
0351             readMenuFolderInfo(subFolderInfo, serviceGroup, folderInfo->fullId);
0352             folderInfo->add(subFolderInfo, true);
0353         } else if (e->isType(KST_KService)) {
0354             const KService::Ptr service(static_cast<KService *>(e.data()));
0355             folderInfo->add(new MenuEntryInfo(service), true);
0356         } else if (e->isType(KST_KServiceSeparator)) {
0357             folderInfo->add(m_separator, true);
0358         }
0359     }
0360 }
0361 
0362 void TreeView::fill()
0363 {
0364     QApplication::setOverrideCursor(Qt::WaitCursor);
0365     clear();
0366     fillBranch(m_rootFolder, nullptr);
0367     QApplication::restoreOverrideCursor();
0368 }
0369 
0370 QString TreeView::findName(KDesktopFile *df, bool deleted)
0371 {
0372     QString name = df->readName();
0373     if (deleted) {
0374         if (name == QLatin1String("empty")) {
0375             name.clear();
0376         }
0377         if (name.isEmpty()) {
0378             bool isLocal = true;
0379             const QStringList files = QStandardPaths::locateAll(df->locationType(), df->fileName(), QStandardPaths::LocateFile);
0380             for (QStringList::ConstIterator it = files.constBegin(); it != files.constEnd(); ++it) {
0381                 if (isLocal) {
0382                     isLocal = false;
0383                     continue;
0384                 }
0385 
0386                 KDesktopFile df2(*it);
0387                 name = df2.readName();
0388 
0389                 if (!name.isEmpty() && (name != QLatin1String("empty"))) {
0390                     return name;
0391                 }
0392             }
0393         }
0394     }
0395     return name;
0396 }
0397 
0398 TreeItem *TreeView::createTreeItem(TreeItem *parent, QTreeWidgetItem *after, MenuFolderInfo *folderInfo, bool m_init)
0399 {
0400     TreeItem *item;
0401     if (parent) {
0402         item = new TreeItem(parent, after, QString(), m_init);
0403     } else {
0404         item = new TreeItem(this, after, QString(), m_init);
0405     }
0406 
0407     item->setMenuFolderInfo(folderInfo);
0408     item->setName(folderInfo->caption);
0409     item->setIcon(0, QIcon::fromTheme(folderInfo->icon));
0410     item->setDirectoryPath(folderInfo->fullId);
0411     item->setHiddenInMenu(folderInfo->hidden);
0412     item->load();
0413     return item;
0414 }
0415 
0416 TreeItem *TreeView::createTreeItem(TreeItem *parent, QTreeWidgetItem *after, MenuEntryInfo *entryInfo, bool m_init)
0417 {
0418     bool hidden = entryInfo->hidden;
0419 
0420     TreeItem *item;
0421     if (parent) {
0422         item = new TreeItem(parent, after, entryInfo->menuId(), m_init);
0423     } else {
0424         item = new TreeItem(this, after, entryInfo->menuId(), m_init);
0425     }
0426 
0427     QString name;
0428 
0429     if (m_detailedMenuEntries && entryInfo->description.length() != 0) {
0430         if (m_detailedEntriesNamesFirst) {
0431             name = entryInfo->caption + QStringLiteral(" (") + entryInfo->description + QLatin1Char(')');
0432         } else {
0433             name = entryInfo->description + QStringLiteral(" (") + entryInfo->caption + QLatin1Char(')');
0434         }
0435     } else {
0436         name = entryInfo->caption;
0437     }
0438 
0439     ////qCDebug(KMENUEDIT_LOG) << parent << after << name;
0440     item->setMenuEntryInfo(entryInfo);
0441     item->setName(name);
0442     item->setIcon(0, QIcon::fromTheme(entryInfo->icon));
0443     item->setHiddenInMenu(hidden);
0444     item->load();
0445 
0446     return item;
0447 }
0448 
0449 TreeItem *TreeView::createTreeItem(TreeItem *parent, QTreeWidgetItem *after, MenuSeparatorInfo *, bool init)
0450 {
0451     TreeItem *item;
0452     if (parent) {
0453         item = new TreeItem(parent, after, QString(), init);
0454     } else {
0455         item = new TreeItem(this, after, QString(), init);
0456     }
0457 
0458     setItemWidget(item, 0, new SeparatorWidget);
0459     return item;
0460 }
0461 
0462 void TreeView::fillBranch(MenuFolderInfo *folderInfo, TreeItem *parent)
0463 {
0464     QString relPath = parent ? parent->directory() : QString();
0465     TreeItem *after = nullptr;
0466     foreach (MenuInfo *info, folderInfo->initialLayout) {
0467         MenuEntryInfo *entry = dynamic_cast<MenuEntryInfo *>(info);
0468         if (entry) {
0469             after = createTreeItem(parent, after, entry);
0470             continue;
0471         }
0472 
0473         MenuFolderInfo *subFolder = dynamic_cast<MenuFolderInfo *>(info);
0474         if (subFolder) {
0475             after = createTreeItem(parent, after, subFolder);
0476             continue;
0477         }
0478         MenuSeparatorInfo *separator = dynamic_cast<MenuSeparatorInfo *>(info);
0479         if (separator) {
0480             after = createTreeItem(parent, after, separator);
0481             continue;
0482         }
0483     }
0484 }
0485 TreeItem *TreeView::expandPath(TreeItem *item, const QString &path)
0486 {
0487     int i = path.indexOf(QLatin1String("/"));
0488     QString subMenu = path.left(i + 1);
0489     QString restMenu = path.mid(i + 1);
0490 
0491     for (int i = 0; i < item->childCount(); ++i) {
0492         TreeItem *childItem = dynamic_cast<TreeItem *>(item->child(i));
0493         if (!childItem) {
0494             continue;
0495         }
0496 
0497         MenuFolderInfo *folderInfo = childItem->folderInfo();
0498         if (folderInfo && (folderInfo->id == subMenu)) {
0499             childItem->setExpanded(true);
0500             if (!restMenu.isEmpty()) {
0501                 return expandPath(childItem, restMenu);
0502             } else {
0503                 return childItem;
0504             }
0505         }
0506     }
0507 
0508     return nullptr;
0509 }
0510 
0511 void TreeView::selectMenu(const QString &menu)
0512 {
0513     // close all parent expansions and deselect everything
0514     collapseAll();
0515     setCurrentIndex(rootIndex());
0516 
0517     if (menu.length() <= 1) {
0518         setCurrentItem(topLevelItem(0));
0519         clearSelection();
0520         return; // Root menu
0521     }
0522 
0523     QString restMenu = menu;
0524     if (menu.startsWith(QLatin1Char('/'))) {
0525         restMenu = menu.mid(1);
0526     }
0527     if (!restMenu.endsWith(QLatin1Char('/'))) {
0528         restMenu += QLatin1Char('/');
0529     }
0530 
0531     TreeItem *item = nullptr;
0532     int i = restMenu.indexOf(QLatin1String("/"));
0533     QString subMenu = restMenu.left(i + 1);
0534     restMenu = restMenu.mid(i + 1);
0535 
0536     for (int i = 0; i < topLevelItemCount(); ++i) {
0537         item = dynamic_cast<TreeItem *>(topLevelItem(i));
0538         if (!item) {
0539             continue;
0540         }
0541 
0542         MenuFolderInfo *folderInfo = item->folderInfo();
0543         if (folderInfo && (folderInfo->id == subMenu)) {
0544             if (!restMenu.isEmpty()) {
0545                 item = expandPath(item, restMenu);
0546             }
0547             break;
0548         }
0549     }
0550 
0551     if (item) {
0552         setCurrentItem(item);
0553         scrollToItem(item);
0554     }
0555 }
0556 
0557 void TreeView::selectMenuEntry(const QString &menuEntry)
0558 {
0559     TreeItem *item = static_cast<TreeItem *>(selectedItem());
0560     if (!item) {
0561         item = static_cast<TreeItem *>(currentItem());
0562     }
0563 
0564     if (!item) {
0565         return;
0566     }
0567 
0568     QTreeWidgetItem *parent = item->parent();
0569     if (parent) {
0570         for (int i = 0; i < parent->childCount(); ++i) {
0571             TreeItem *item = dynamic_cast<TreeItem *>(parent->child(i));
0572             if (!item || item->isDirectory()) {
0573                 continue;
0574             }
0575 
0576             MenuEntryInfo *entry = item->entryInfo();
0577             if (entry && entry->menuId() == menuEntry) {
0578                 setCurrentItem(item);
0579                 scrollToItem(item);
0580                 return;
0581             }
0582         }
0583     } else {
0584         // top level
0585         for (int i = 0; i < topLevelItemCount(); ++i) {
0586             TreeItem *item = dynamic_cast<TreeItem *>(topLevelItem(i));
0587             if (!item || item->isDirectory()) {
0588                 continue;
0589             }
0590 
0591             MenuEntryInfo *entry = item->entryInfo();
0592             if (entry && entry->menuId() == menuEntry) {
0593                 setCurrentItem(item);
0594                 scrollToItem(item);
0595                 return;
0596             }
0597         }
0598     }
0599 }
0600 
0601 void TreeView::itemSelected(QTreeWidgetItem *item)
0602 {
0603     if (item) {
0604         // ensure the item is visible as selected
0605         item->setSelected(true);
0606     }
0607 
0608     TreeItem *_item = item ? static_cast<TreeItem *>(item) : nullptr;
0609     TreeItem *parentItem = nullptr;
0610     bool selected = false;
0611     bool dselected = false;
0612     if (_item) {
0613         selected = true;
0614         dselected = _item->isHiddenInMenu();
0615         parentItem = getParentItem(_item);
0616     }
0617 
0618     // change actions activation
0619     m_ac->action(CUT_ACTION_NAME)->setEnabled(selected);
0620     m_ac->action(COPY_ACTION_NAME)->setEnabled(selected);
0621     m_ac->action(PASTE_ACTION_NAME)->setEnabled(m_clipboard != 0);
0622 
0623     if (m_ac->action(DELETE_ACTION_NAME)) {
0624         m_ac->action(DELETE_ACTION_NAME)->setEnabled(selected && !dselected);
0625     }
0626 
0627     m_ac->action(SORT_BY_NAME_ACTION_NAME)->setEnabled(selected && _item && _item->isDirectory() && (_item->childCount() > 0));
0628     m_ac->action(SORT_BY_DESCRIPTION_ACTION_NAME)->setEnabled(m_ac->action(SORT_BY_NAME_ACTION_NAME)->isEnabled());
0629 
0630     m_ac->action(MOVE_UP_ACTION_NAME)->setEnabled(_item && selected && (parentItem->indexOfChild(_item) > 0));
0631     m_ac->action(MOVE_DOWN_ACTION_NAME)->setEnabled(_item && selected && (parentItem->indexOfChild(_item) < parentItem->childCount() - 1));
0632 
0633     if (!item) {
0634         emit disableAction();
0635         return;
0636     }
0637 
0638     if (_item) {
0639         if (_item->isDirectory()) {
0640             emit entrySelected(_item->folderInfo());
0641         } else {
0642             emit entrySelected(_item->entryInfo());
0643         }
0644     }
0645 }
0646 
0647 void TreeView::currentDataChanged(MenuFolderInfo *folderInfo)
0648 {
0649     TreeItem *item = (TreeItem *)selectedItem();
0650     if (!item || !folderInfo) {
0651         return;
0652     }
0653 
0654     item->setName(folderInfo->caption);
0655     item->setIcon(0, QIcon::fromTheme(folderInfo->icon));
0656 }
0657 
0658 void TreeView::currentDataChanged(MenuEntryInfo *entryInfo)
0659 {
0660     TreeItem *item = (TreeItem *)selectedItem();
0661     if (!item || !entryInfo) {
0662         return;
0663     }
0664 
0665     QString name;
0666 
0667     if (m_detailedMenuEntries && !entryInfo->description.isEmpty()) {
0668         if (m_detailedEntriesNamesFirst) {
0669             name = entryInfo->caption + QStringLiteral(" (") + entryInfo->description + QLatin1Char(')');
0670         } else {
0671             name = entryInfo->description + QStringLiteral(" (") + entryInfo->caption + QLatin1Char(')');
0672         }
0673     } else {
0674         name = entryInfo->caption;
0675     }
0676 
0677     item->setName(name);
0678     item->setIcon(0, QIcon::fromTheme(entryInfo->icon));
0679 }
0680 
0681 Qt::DropActions TreeView::supportedDropActions() const
0682 {
0683     return Qt::CopyAction | Qt::MoveAction;
0684 }
0685 
0686 QStringList TreeView::mimeTypes() const
0687 {
0688     return m_dropMimeTypes;
0689 }
0690 
0691 void TreeView::startDrag(Qt::DropActions supportedActions)
0692 {
0693     QList<QTreeWidgetItem *> items;
0694     items << selectedItem();
0695     QMimeData *data = mimeData(items);
0696     if (!data) {
0697         return;
0698     }
0699 
0700     QDrag *drag = new QDrag(this);
0701     const int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
0702     drag->setPixmap(selectedItem()->icon(0).pixmap(iconSize, iconSize));
0703     drag->setMimeData(data);
0704     drag->exec(supportedActions, Qt::MoveAction);
0705 }
0706 
0707 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0708 QMimeData *TreeView::mimeData(const QList<QTreeWidgetItem *> items) const
0709 #else
0710 QMimeData *TreeView::mimeData(const QList<QTreeWidgetItem *> &items) const
0711 #endif
0712 {
0713     if (items.isEmpty()) {
0714         return nullptr;
0715     }
0716 
0717     return new MenuItemMimeData(dynamic_cast<TreeItem *>(items.first()));
0718 }
0719 
0720 static QString createDesktopFile(const QString &file, QString *menuId, QStringList *excludeList)
0721 {
0722     QString base = file.mid(file.lastIndexOf(QLatin1Char('/')) + 1);
0723     base = base.left(base.lastIndexOf(QLatin1Char('.')));
0724 
0725     const QRegularExpression re(QStringLiteral("(.*)(?=-\\d+)"));
0726     const QRegularExpressionMatch match = re.match(base);
0727     base = match.hasMatch() ? match.captured(1) : base;
0728 
0729     QString result = KService::newServicePath(true, base, menuId, excludeList);
0730     excludeList->append(*menuId);
0731     // Todo for Undo-support: Undo menuId allocation:
0732 
0733     return result;
0734 }
0735 
0736 static KDesktopFile *copyDesktopFile(MenuEntryInfo *entryInfo, QString *menuId, QStringList *excludeList)
0737 {
0738     QString result = createDesktopFile(entryInfo->file(), menuId, excludeList);
0739     KDesktopFile *df = entryInfo->desktopFile()->copyTo(result);
0740     df->desktopGroup().deleteEntry("Categories"); // Don't set any categories!
0741 
0742     return df;
0743 }
0744 
0745 static QString createDirectoryFile(const QString &file, QStringList *excludeList)
0746 {
0747     QString base = file.mid(file.lastIndexOf(QLatin1Char('/')) + 1);
0748     base = base.left(base.lastIndexOf(QLatin1Char('.')));
0749 
0750     QString result;
0751     int i = 1;
0752     while (true) {
0753         if (i == 1) {
0754             result = base + QStringLiteral(".directory");
0755         } else {
0756             result = base + QStringLiteral("-%1.directory").arg(i);
0757         }
0758 
0759         if (!excludeList->contains(result)) {
0760             if (QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("desktop-directories/") + result).isEmpty()) {
0761                 break;
0762             }
0763         }
0764         i++;
0765     }
0766     excludeList->append(result);
0767     result = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/desktop-directories/") + result;
0768     return result;
0769 }
0770 
0771 bool TreeView::dropMimeData(QTreeWidgetItem *item, int index, const QMimeData *data, Qt::DropAction action)
0772 {
0773     // get destination folder
0774     TreeItem *titem = item ? dynamic_cast<TreeItem *>(item) : nullptr;
0775     if (item && !titem) {
0776         return false;
0777     }
0778 
0779     TreeItem *parentItem = nullptr;
0780     QTreeWidgetItem *after = titem;
0781     // find the parent item and which item the dropped item should go after
0782     if (titem) {
0783         if (titem->isDirectory()) {
0784             parentItem = titem;
0785             after = titem->child(index);
0786             if (!after) {
0787                 after = titem->child(titem->childCount() - 1);
0788             }
0789         } else {
0790             parentItem = dynamic_cast<TreeItem *>(titem->parent());
0791             if (titem->parent() && !parentItem) {
0792                 return false;
0793             }
0794         }
0795     } else if (index > 0) {
0796         after = topLevelItem(index);
0797         if (!after) {
0798             after = topLevelItem(topLevelItemCount() - 1);
0799         }
0800     }
0801 
0802     QString folder = parentItem ? parentItem->directory() : QStringLiteral("/");
0803     MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
0804     // qCDebug(KMENUEDIT_LOG) << "think we're dropping on" << (parentItem ? parentItem->text(0) : "Top Level") <<  index;
0805 
0806     if (!data->hasFormat(QLatin1String(s_internalMimeType))) {
0807         // External drop
0808         if (!data->hasUrls()) {
0809             return false;
0810         }
0811 
0812         QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(data);
0813         if (urls.isEmpty() || !urls[0].isLocalFile()) {
0814             return false;
0815         }
0816 
0817         // FIXME: this should really support multiple DnD
0818         QString path = urls[0].path();
0819         if (!path.endsWith(QLatin1String(".desktop"))) {
0820             return false;
0821         }
0822 
0823         QString menuId;
0824         QString result = createDesktopFile(path, &menuId, &m_newMenuIds);
0825         KDesktopFile orig_df(path);
0826         KDesktopFile *df = orig_df.copyTo(result);
0827         df->desktopGroup().deleteEntry("Categories"); // Don't set any categories!
0828 
0829         KService::Ptr s(new KService(df));
0830         s->setMenuId(menuId);
0831 
0832         MenuEntryInfo *entryInfo = new MenuEntryInfo(s, df);
0833 
0834         QString oldCaption = entryInfo->caption;
0835         QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption);
0836         entryInfo->setCaption(newCaption);
0837 
0838         // Add file to menu
0839         // m_menuFile->addEntry(folder, menuId);
0840         m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
0841 
0842         // create the TreeItem
0843         if (parentItem) {
0844             parentItem->setExpanded(true);
0845         }
0846 
0847         // update fileInfo data
0848         parentFolderInfo->add(entryInfo);
0849 
0850         TreeItem *newItem = createTreeItem(parentItem, after, entryInfo, true);
0851         setCurrentItem(newItem);
0852 
0853         setLayoutDirty(parentItem);
0854         return true;
0855     }
0856 
0857     QVariant p(data->data(QLatin1String(s_internalMimeType)));
0858     const MenuItemMimeData *itemData = dynamic_cast<const MenuItemMimeData *>(data);
0859     if (!itemData) {
0860         return false;
0861     }
0862 
0863     TreeItem *dragItem = itemData->item();
0864     if (!dragItem || dragItem == after) {
0865         return false; // Nothing to do
0866     }
0867 
0868     ////qCDebug(KMENUEDIT_LOG) << "an internal drag of" << dragItem->text(0) << (parentItem ? parentItem->text(0) : "Top level");
0869     if (dragItem->isDirectory()) {
0870         MenuFolderInfo *folderInfo = dragItem->folderInfo();
0871         if (action == Qt::CopyAction) {
0872             // FIXME:
0873             // * Create new .directory file
0874         } else {
0875             TreeItem *tmpItem = static_cast<TreeItem *>(parentItem);
0876             while (tmpItem) {
0877                 if (tmpItem == dragItem) {
0878                     return false;
0879                 }
0880 
0881                 tmpItem = static_cast<TreeItem *>(tmpItem->parent());
0882             }
0883 
0884             // Remove MenuFolderInfo
0885             TreeItem *oldParentItem = static_cast<TreeItem *>(dragItem->parent());
0886             MenuFolderInfo *oldParentFolderInfo = oldParentItem ? oldParentItem->folderInfo() : m_rootFolder;
0887             oldParentFolderInfo->take(folderInfo);
0888 
0889             // Move menu
0890             QString oldFolder = folderInfo->fullId;
0891             QString folderName = folderInfo->id;
0892             QString newFolder = m_menuFile->uniqueMenuName(folder, folderName, parentFolderInfo->existingMenuIds());
0893             folderInfo->id = newFolder;
0894 
0895             // Add file to menu
0896             // m_menuFile->moveMenu(oldFolder, folder + newFolder);
0897             // qCDebug(KMENUEDIT_LOG) << "moving" << dragItem->text(0) << "to" << folder + newFolder;
0898             m_menuFile->pushAction(MenuFile::MOVE_MENU, oldFolder, folder + newFolder);
0899 
0900             // Make sure caption is unique
0901             QString newCaption = parentFolderInfo->uniqueMenuCaption(folderInfo->caption);
0902             if (newCaption != folderInfo->caption) {
0903                 folderInfo->setCaption(newCaption);
0904             }
0905 
0906             // create the TreeItem
0907             if (parentItem) {
0908                 parentItem->setExpanded(true);
0909             }
0910 
0911             // update fileInfo data
0912             folderInfo->updateFullId(parentFolderInfo->fullId);
0913             folderInfo->setInUse(true);
0914             parentFolderInfo->add(folderInfo);
0915 
0916             if (parentItem != oldParentItem) {
0917                 if (oldParentItem) {
0918                     oldParentItem->takeChild(oldParentItem->indexOfChild(dragItem));
0919                 } else {
0920                     takeTopLevelItem(indexOfTopLevelItem(dragItem));
0921                 }
0922             }
0923 
0924             if (parentItem) {
0925                 parentItem->insertChild(after ? parentItem->indexOfChild(after) + 1 : parentItem->childCount(), dragItem);
0926             } else {
0927                 insertTopLevelItem(after ? indexOfTopLevelItem(after) : topLevelItemCount(), dragItem);
0928             }
0929 
0930             dragItem->setName(folderInfo->caption);
0931             dragItem->setDirectoryPath(folderInfo->fullId);
0932             setCurrentItem(dragItem);
0933         }
0934     } else if (dragItem->isEntry()) {
0935         MenuEntryInfo *entryInfo = dragItem->entryInfo();
0936         QString menuId = entryInfo->menuId();
0937 
0938         if (action == Qt::CopyAction) {
0939             // Need to copy file and then add it
0940             KDesktopFile *df = copyDesktopFile(entryInfo, &menuId, &m_newMenuIds); // Duplicate
0941             // UNDO-ACTION: NEW_MENU_ID (menuId)
0942 
0943             KService::Ptr s(new KService(df));
0944             s->setMenuId(menuId);
0945 
0946             entryInfo = new MenuEntryInfo(s, df);
0947 
0948             QString oldCaption = entryInfo->caption;
0949             QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption);
0950             entryInfo->setCaption(newCaption);
0951         } else {
0952             del(dragItem, false);
0953             QString oldCaption = entryInfo->caption;
0954             QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption);
0955             entryInfo->setCaption(newCaption);
0956             entryInfo->setInUse(true);
0957         }
0958 
0959         // Add file to menu
0960         // m_menuFile->addEntry(folder, menuId);
0961         m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
0962 
0963         // create the TreeItem
0964         if (parentItem) {
0965             parentItem->setExpanded(true);
0966         }
0967 
0968         // update fileInfo data
0969         parentFolderInfo->add(entryInfo);
0970 
0971         TreeItem *newItem = createTreeItem(parentItem, after, entryInfo);
0972         setCurrentItem(newItem);
0973 
0974         // clipboard no longer owns entryInfo
0975         if (m_clipboardEntryInfo == entryInfo) {
0976             m_clipboard = COPY_FILE;
0977         }
0978     } else {
0979         // copying a separator
0980         if (action != Qt::CopyAction) {
0981             del(dragItem, false);
0982         }
0983 
0984         TreeItem *newItem = createTreeItem(parentItem, after, m_separator);
0985         setCurrentItem(newItem);
0986     }
0987 
0988     // qCDebug(KMENUEDIT_LOG) << "setting the layout to be dirty at" << parentItem;
0989     setLayoutDirty(parentItem);
0990     return true;
0991 }
0992 
0993 QTreeWidgetItem *TreeView::selectedItem()
0994 {
0995     QList<QTreeWidgetItem *> selection = selectedItems();
0996 
0997     if (selection.isEmpty()) {
0998         return nullptr;
0999     }
1000 
1001     return selection.first();
1002 }
1003 
1004 void TreeView::contextMenuEvent(QContextMenuEvent *event)
1005 {
1006     if (m_popupMenu && itemAt(event->pos())) {
1007         m_popupMenu->exec(event->globalPos());
1008     }
1009 }
1010 
1011 void TreeView::dropEvent(QDropEvent *event)
1012 {
1013     // this prevents QTreeWidget from interfering with our moves
1014     QTreeView::dropEvent(event);
1015 }
1016 
1017 void TreeView::newsubmenu()
1018 {
1019     TreeItem *parentItem = nullptr;
1020     TreeItem *item = (TreeItem *)selectedItem();
1021 
1022     bool ok;
1023     QString caption = QInputDialog::getText(this, i18n("New Submenu"), i18n("Submenu name:"), QLineEdit::Normal, QString(), &ok);
1024 
1025     if (!ok) {
1026         return;
1027     }
1028 
1029     QString file = caption;
1030     file.replace(QLatin1Char('/'), QLatin1Char('-'));
1031 
1032     file = createDirectoryFile(file, &m_newDirectoryList); // Create
1033 
1034     // get destination folder
1035     QString folder;
1036 
1037     if (!item) {
1038         parentItem = nullptr;
1039         folder.clear();
1040     } else if (item->isDirectory()) {
1041         parentItem = item;
1042         item = nullptr;
1043         folder = parentItem->directory();
1044     } else {
1045         parentItem = static_cast<TreeItem *>(item->parent());
1046         folder = parentItem ? parentItem->directory() : QString();
1047     }
1048 
1049     MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1050     MenuFolderInfo *folderInfo = new MenuFolderInfo();
1051     folderInfo->caption = parentFolderInfo->uniqueMenuCaption(caption);
1052     folderInfo->id = m_menuFile->uniqueMenuName(folder, caption, parentFolderInfo->existingMenuIds());
1053     folderInfo->directoryFile = file;
1054     folderInfo->icon = QStringLiteral("package");
1055     folderInfo->hidden = false;
1056     folderInfo->setDirty();
1057 
1058     KDesktopFile *df = new KDesktopFile(file);
1059     KConfigGroup desktopGroup = df->desktopGroup();
1060     desktopGroup.writeEntry("Name", folderInfo->caption);
1061     desktopGroup.writeEntry("Icon", folderInfo->icon);
1062     df->sync();
1063     delete df;
1064     // Add file to menu
1065     // m_menuFile->addMenu(folder + folderInfo->id, file);
1066     m_menuFile->pushAction(MenuFile::ADD_MENU, folder + folderInfo->id, file);
1067 
1068     folderInfo->fullId = parentFolderInfo->fullId + folderInfo->id;
1069 
1070     // create the TreeItem
1071     if (parentItem) {
1072         parentItem->setExpanded(true);
1073     }
1074 
1075     // update fileInfo data
1076     parentFolderInfo->add(folderInfo);
1077 
1078     TreeItem *newItem = createTreeItem(parentItem, item, folderInfo, true);
1079 
1080     setCurrentItem(newItem);
1081     setLayoutDirty(parentItem);
1082 }
1083 
1084 void TreeView::newitem()
1085 {
1086     TreeItem *parentItem = nullptr;
1087     TreeItem *item = (TreeItem *)selectedItem();
1088 
1089     bool ok;
1090     QString caption = QInputDialog::getText(this, i18n("New Item"), i18n("Item name:"), QLineEdit::Normal, QString(), &ok);
1091 
1092     if (!ok) {
1093         return;
1094     }
1095 
1096     QString menuId;
1097     QString file = caption;
1098     file.replace(QLatin1Char('/'), QLatin1Char('-'));
1099 
1100     file = createDesktopFile(file, &menuId, &m_newMenuIds); // Create
1101 
1102     KDesktopFile *df = new KDesktopFile(file);
1103     KConfigGroup desktopGroup = df->desktopGroup();
1104     desktopGroup.writeEntry("Name", caption);
1105     desktopGroup.writeEntry("Type", "Application");
1106 
1107     // get destination folder
1108     QString folder;
1109 
1110     if (!item) {
1111         parentItem = nullptr;
1112         folder.clear();
1113     } else if (item->isDirectory()) {
1114         parentItem = item;
1115         item = nullptr;
1116         folder = parentItem->directory();
1117     } else {
1118         parentItem = static_cast<TreeItem *>(item->parent());
1119         folder = parentItem ? parentItem->directory() : QString();
1120     }
1121 
1122     MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1123 
1124     // Add file to menu
1125     // m_menuFile->addEntry(folder, menuId);
1126     m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
1127 
1128     KService::Ptr s(new KService(df));
1129     s->setMenuId(menuId);
1130 
1131     MenuEntryInfo *entryInfo = new MenuEntryInfo(s, df);
1132 
1133     // create the TreeItem
1134     if (parentItem) {
1135         parentItem->setExpanded(true);
1136     }
1137 
1138     // update fileInfo data
1139     parentFolderInfo->add(entryInfo);
1140 
1141     TreeItem *newItem = createTreeItem(parentItem, item, entryInfo, true);
1142 
1143     setCurrentItem(newItem);
1144     setLayoutDirty(parentItem);
1145 }
1146 
1147 void TreeView::newsep()
1148 {
1149     TreeItem *parentItem = nullptr;
1150     TreeItem *item = (TreeItem *)selectedItem();
1151 
1152     if (!item) {
1153         parentItem = nullptr;
1154     } else if (item->isDirectory()) {
1155         parentItem = item;
1156         item = nullptr;
1157     } else {
1158         parentItem = static_cast<TreeItem *>(item->parent());
1159     }
1160 
1161     // create the TreeItem
1162     if (parentItem) {
1163         parentItem->setExpanded(true);
1164     }
1165 
1166     TreeItem *newItem = createTreeItem(parentItem, item, m_separator, true);
1167 
1168     setCurrentItem(newItem);
1169     setLayoutDirty(parentItem);
1170 }
1171 
1172 void TreeView::cut()
1173 {
1174     copy(true);
1175 
1176     // Select new current item
1177     // TODO: is this completely redundant?
1178     setCurrentItem(currentItem());
1179 }
1180 
1181 void TreeView::copy()
1182 {
1183     copy(false);
1184 }
1185 
1186 void TreeView::copy(bool cutting)
1187 {
1188     TreeItem *item = (TreeItem *)selectedItem();
1189 
1190     // nil selected? -> nil to copy
1191     if (item == nullptr) {
1192         return;
1193     }
1194 
1195     if (cutting) {
1196         setLayoutDirty((TreeItem *)item->parent());
1197     }
1198 
1199     // clean up old stuff
1200     cleanupClipboard();
1201 
1202     // is item a folder or a file?
1203     if (item->isDirectory()) {
1204         QString folder = item->directory();
1205         if (cutting) {
1206             // Place in clipboard
1207             m_clipboard = MOVE_FOLDER;
1208             m_clipboardFolderInfo = item->folderInfo();
1209 
1210             del(item, false);
1211         } else {
1212             // Place in clipboard
1213             m_clipboard = COPY_FOLDER;
1214             m_clipboardFolderInfo = item->folderInfo();
1215         }
1216     } else if (item->isEntry()) {
1217         if (cutting) {
1218             // Place in clipboard
1219             m_clipboard = MOVE_FILE;
1220             m_clipboardEntryInfo = item->entryInfo();
1221 
1222             del(item, false);
1223         } else {
1224             // Place in clipboard
1225             m_clipboard = COPY_FILE;
1226             m_clipboardEntryInfo = item->entryInfo();
1227         }
1228     } else {
1229         // Place in clipboard
1230         m_clipboard = COPY_SEPARATOR;
1231         if (cutting) {
1232             del(item, false);
1233         }
1234     }
1235 
1236     m_ac->action(PASTE_ACTION_NAME)->setEnabled(true);
1237 }
1238 
1239 void TreeView::paste()
1240 {
1241     TreeItem *parentItem = nullptr;
1242     TreeItem *item = (TreeItem *)selectedItem();
1243 
1244     // nil selected? -> nil to paste to
1245     if (item == nullptr) {
1246         return;
1247     }
1248 
1249     // is there content in the clipboard?
1250     if (!m_clipboard) {
1251         return;
1252     }
1253 
1254     // get destination folder
1255     QString folder;
1256 
1257     if (item->isDirectory()) {
1258         parentItem = item;
1259         item = nullptr;
1260         folder = parentItem->directory();
1261     } else {
1262         parentItem = static_cast<TreeItem *>(item->parent());
1263         folder = parentItem ? parentItem->directory() : QString();
1264     }
1265 
1266     MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1267     int command = m_clipboard;
1268     if ((command == COPY_FOLDER) || (command == MOVE_FOLDER)) {
1269         MenuFolderInfo *folderInfo = m_clipboardFolderInfo;
1270         if (command == COPY_FOLDER) {
1271             // Ugh.. this is hard :)
1272             // * Create new .directory file
1273             // Add
1274         } else if (command == MOVE_FOLDER) {
1275             // Move menu
1276             QString oldFolder = folderInfo->fullId;
1277             QString folderName = folderInfo->id;
1278             QString newFolder = m_menuFile->uniqueMenuName(folder, folderName, parentFolderInfo->existingMenuIds());
1279             folderInfo->id = newFolder;
1280 
1281             // Add file to menu
1282             // m_menuFile->moveMenu(oldFolder, folder + newFolder);
1283             m_menuFile->pushAction(MenuFile::MOVE_MENU, oldFolder, folder + newFolder);
1284 
1285             // Make sure caption is unique
1286             QString newCaption = parentFolderInfo->uniqueMenuCaption(folderInfo->caption);
1287             if (newCaption != folderInfo->caption) {
1288                 folderInfo->setCaption(newCaption);
1289             }
1290             // create the TreeItem
1291             if (parentItem) {
1292                 parentItem->setExpanded(true);
1293             }
1294 
1295             // update fileInfo data
1296             folderInfo->fullId = parentFolderInfo->fullId + folderInfo->id;
1297             folderInfo->setInUse(true);
1298             parentFolderInfo->add(folderInfo);
1299 
1300             TreeItem *newItem = createTreeItem(parentItem, item, folderInfo);
1301 
1302             setCurrentItem(newItem);
1303         }
1304 
1305         m_clipboard = COPY_FOLDER; // Next one copies.
1306     } else if ((command == COPY_FILE) || (command == MOVE_FILE)) {
1307         MenuEntryInfo *entryInfo = m_clipboardEntryInfo;
1308         QString menuId;
1309 
1310         if (command == COPY_FILE) {
1311             // Need to copy file and then add it
1312             KDesktopFile *df = copyDesktopFile(entryInfo, &menuId, &m_newMenuIds); // Duplicate
1313 
1314             KService::Ptr s(new KService(df));
1315             s->setMenuId(menuId);
1316             entryInfo = new MenuEntryInfo(s, df);
1317 
1318             QString oldCaption = entryInfo->caption;
1319             QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption);
1320             entryInfo->setCaption(newCaption);
1321         } else if (command == MOVE_FILE) {
1322             menuId = entryInfo->menuId();
1323             m_clipboard = COPY_FILE; // Next one copies.
1324 
1325             QString oldCaption = entryInfo->caption;
1326             QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption);
1327             entryInfo->setCaption(newCaption);
1328             entryInfo->setInUse(true);
1329         }
1330         // Add file to menu
1331         // m_menuFile->addEntry(folder, menuId);
1332         m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
1333 
1334         // create the TreeItem
1335         if (parentItem) {
1336             parentItem->setExpanded(true);
1337         }
1338 
1339         // update fileInfo data
1340         parentFolderInfo->add(entryInfo);
1341 
1342         TreeItem *newItem = createTreeItem(parentItem, item, entryInfo, true);
1343 
1344         setCurrentItem(newItem);
1345     } else {
1346         // create separator
1347         if (parentItem) {
1348             parentItem->setExpanded(true);
1349         }
1350 
1351         TreeItem *newItem = createTreeItem(parentItem, item, m_separator, true);
1352 
1353         setCurrentItem(newItem);
1354     }
1355     setLayoutDirty(parentItem);
1356 }
1357 
1358 /**
1359  * This slot is called from the signal mapper to sort children contained in an item.
1360  * This item is determined according to the chosen sort type.
1361  *
1362  * @brief Determine which item is to sort, and do it.
1363  * @param sortCmd Sort type.
1364  */
1365 void TreeView::sort(const int sortCmd)
1366 {
1367     // determine the chosen sort type and the selected item
1368     SortType sortType = static_cast<SortType>(sortCmd);
1369     TreeItem *itemToSort;
1370     if (sortType == SortByName || sortType == SortByDescription) {
1371         itemToSort = static_cast<TreeItem *>(selectedItem());
1372         sortItem(itemToSort, sortType);
1373     } else if (sortType == SortAllByDescription) {
1374         sortType = SortByDescription;
1375         for (int i = 0; i < topLevelItemCount(); ++i) {
1376             itemToSort = static_cast<TreeItem *>(topLevelItem(i));
1377             sortItem(itemToSort, sortType);
1378         }
1379     } else { /* if (sortType == SortAllByName) */
1380         sortType = SortByName;
1381         for (int i = 0; i < topLevelItemCount(); ++i) {
1382             itemToSort = static_cast<TreeItem *>(topLevelItem(i));
1383             sortItem(itemToSort, sortType);
1384         }
1385     }
1386 }
1387 
1388 /**
1389  * Sort children of the given item, according to the sort type.
1390  * The sorting is done on children groups, splited by separator items.
1391  *
1392  * @brief Sort item children.
1393  * @param item Item to sort.
1394  * @param sortType Sort type.
1395  */
1396 void TreeView::sortItem(TreeItem *item, SortType sortType)
1397 {
1398     // sort the selected item only if contains children
1399     if ((!item->isDirectory()) || (item->childCount() == 0)) {
1400         return;
1401     }
1402 
1403     // remove contained children
1404     QList<QTreeWidgetItem *> children = item->takeChildren();
1405 
1406     // sort children groups, splited by separator items
1407     QList<QTreeWidgetItem *>::iterator startIt = children.begin();
1408     QList<QTreeWidgetItem *>::iterator currentIt = children.begin();
1409     while (currentIt != children.end()) {
1410         TreeItem *child = static_cast<TreeItem *>(*currentIt);
1411         // if it's a separator, sort previous items and continue on following items
1412         if (child->isSeparator() && startIt != currentIt) {
1413             sortItemChildren(startIt, currentIt, sortType);
1414             startIt = currentIt + 1;
1415         }
1416         ++currentIt;
1417     }
1418     sortItemChildren(startIt, currentIt, sortType);
1419 
1420     // insert sorted children in the tree
1421     item->addChildren(children);
1422     foreach (QTreeWidgetItem *child, children) {
1423         // recreate item widget for separators
1424         TreeItem *treeItem = static_cast<TreeItem *>(child);
1425         if (treeItem->isSeparator()) {
1426             setItemWidget(treeItem, 0, new SeparatorWidget);
1427         }
1428 
1429         // try to sort sub-children
1430         sortItem(static_cast<TreeItem *>(child), sortType);
1431     }
1432 
1433     // flag current item as dirty
1434     TreeItem *itemToFlagAsDirty = item;
1435     // if tree root item, set the entire layout as dirty
1436     if (item == invisibleRootItem()) {
1437         itemToFlagAsDirty = nullptr;
1438     }
1439     setLayoutDirty(itemToFlagAsDirty);
1440 }
1441 
1442 /**
1443  * Sort a children range defined with two list iterators, according to the sort type.
1444  *
1445  * @brief Sort a children range.
1446  * @param begin First child iterator.
1447  * @param end Last child iterator (exclusive, pointed child won't be affected).
1448  * @param sortType Sort type.
1449  */
1450 void TreeView::sortItemChildren(const QList<QTreeWidgetItem *>::iterator &begin, const QList<QTreeWidgetItem *>::iterator &end, SortType sortType)
1451 {
1452     // sort by name
1453     if (sortType == SortByName) {
1454         std::sort(begin, end, TreeItem::itemNameLessThan);
1455     }
1456     // sort by description
1457     else if (sortType == SortByDescription) {
1458         std::sort(begin, end, TreeItem::itemDescriptionLessThan);
1459     }
1460 }
1461 
1462 /**
1463  * @brief Move up the selected item.
1464  */
1465 void TreeView::moveUpItem()
1466 {
1467     moveUpOrDownItem(true);
1468 }
1469 
1470 /**
1471  * @brief Move down the selected item.
1472  */
1473 void TreeView::moveDownItem()
1474 {
1475     moveUpOrDownItem(false);
1476 }
1477 
1478 /**
1479  * Move the selected item on desired direction (up or down).
1480  *
1481  * @brief Move up/down the selected item.
1482  * @param isMovingUpAction True to move up, false to move down.
1483  */
1484 void TreeView::moveUpOrDownItem(bool isMovingUpAction)
1485 {
1486     // get the selected item and its parent
1487     TreeItem *sourceItem = static_cast<TreeItem *>(selectedItem());
1488     if (!sourceItem) {
1489         return;
1490     }
1491     TreeItem *parentItem = getParentItem(sourceItem);
1492 
1493     // get selected item index
1494     int sourceItemIndex = parentItem->indexOfChild(sourceItem);
1495 
1496     // find the second item to swap
1497     TreeItem *destItem = nullptr;
1498     int destIndex;
1499     if (isMovingUpAction) {
1500         destIndex = sourceItemIndex - 1;
1501         destItem = static_cast<TreeItem *>(parentItem->child(destIndex));
1502     } else {
1503         destIndex = sourceItemIndex + 1;
1504         destItem = static_cast<TreeItem *>(parentItem->child(destIndex));
1505     }
1506 
1507     // swap items
1508     parentItem->removeChild(sourceItem);
1509     parentItem->insertChild(destIndex, sourceItem);
1510 
1511     // recreate item widget for separators
1512     if (sourceItem->isSeparator()) {
1513         setItemWidget(sourceItem, 0, new SeparatorWidget);
1514     }
1515     if (destItem->isSeparator()) {
1516         setItemWidget(destItem, 0, new SeparatorWidget);
1517     }
1518 
1519     // set the focus on the source item
1520     setCurrentItem(sourceItem);
1521 
1522     // flag parent item as dirty (if the parent is the root item, set the entire layout as dirty)
1523     if (parentItem == invisibleRootItem()) {
1524         parentItem = nullptr;
1525     }
1526     setLayoutDirty(parentItem);
1527 }
1528 
1529 /**
1530  * For a given item, return its parent. For top items, return the invisible root item.
1531  *
1532  * @brief Get the parent item.
1533  * @param item Item.
1534  * @return Parent item.
1535  */
1536 TreeItem *TreeView::getParentItem(QTreeWidgetItem *item) const
1537 {
1538     QTreeWidgetItem *parentItem = item->parent();
1539     if (!parentItem) {
1540         parentItem = invisibleRootItem();
1541     }
1542     return static_cast<TreeItem *>(parentItem);
1543 }
1544 
1545 void TreeView::del()
1546 {
1547     TreeItem *item = (TreeItem *)selectedItem();
1548 
1549     // nil selected? -> nil to delete
1550     if (item == nullptr) {
1551         return;
1552     }
1553 
1554     del(item, true);
1555 
1556     // Select new current item
1557     // TODO: is this completely redundant?
1558     setCurrentItem(currentItem());
1559 }
1560 
1561 void TreeView::del(TreeItem *item, bool deleteInfo)
1562 {
1563     TreeItem *parentItem = static_cast<TreeItem *>(item->parent());
1564     // is file a .directory or a .desktop file
1565     if (item->isDirectory()) {
1566         if (KMessageBox::questionTwoActions(this,
1567                                             i18n("All submenus of '%1' will be removed. Do you want to continue?", item->name()),
1568                                             i18nc("@title:window", "Delete"),
1569                                             KStandardGuiItem::del(),
1570                                             KStandardGuiItem::cancel())
1571             == KMessageBox::SecondaryAction) {
1572             return;
1573         }
1574 
1575         MenuFolderInfo *folderInfo = item->folderInfo();
1576 
1577         // Remove MenuFolderInfo
1578         MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1579         parentFolderInfo->take(folderInfo);
1580         folderInfo->setInUse(false);
1581 
1582         if (m_clipboard == COPY_FOLDER && (m_clipboardFolderInfo == folderInfo)) {
1583             // Copy + Del == Cut
1584             m_clipboard = MOVE_FOLDER; // Clipboard now owns folderInfo
1585         } else {
1586             if (folderInfo->takeRecursive(m_clipboardFolderInfo)) {
1587                 m_clipboard = MOVE_FOLDER; // Clipboard now owns m_clipboardFolderInfo
1588             }
1589             if (deleteInfo) {
1590                 delete folderInfo; // Delete folderInfo
1591             }
1592         }
1593 
1594         // Remove from menu
1595         // m_menuFile->removeMenu(item->directory());
1596         m_menuFile->pushAction(MenuFile::REMOVE_MENU, item->directory(), QString());
1597 
1598         // Remove tree item
1599         delete item;
1600     } else if (item->isEntry()) {
1601         MenuEntryInfo *entryInfo = item->entryInfo();
1602         QString menuId = entryInfo->menuId();
1603 
1604         // Remove MenuFolderInfo
1605         MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1606         parentFolderInfo->take(entryInfo);
1607         entryInfo->setInUse(false);
1608 
1609         if (m_clipboard == COPY_FILE && (m_clipboardEntryInfo == entryInfo)) {
1610             // Copy + Del == Cut
1611             m_clipboard = MOVE_FILE; // Clipboard now owns entryInfo
1612         } else {
1613             if (deleteInfo) {
1614                 delete entryInfo; // Delete entryInfo
1615             }
1616         }
1617 
1618         // Remove from menu
1619         QString folder = parentItem ? parentItem->directory() : QString();
1620         // m_menuFile->removeEntry(folder, menuId);
1621         m_menuFile->pushAction(MenuFile::REMOVE_ENTRY, folder, menuId);
1622 
1623         // Remove tree item
1624         delete item;
1625     } else {
1626         // Remove separator
1627         delete item;
1628     }
1629 
1630     setLayoutDirty(parentItem);
1631 }
1632 
1633 void TreeView::cleanupClipboard()
1634 {
1635     if (m_clipboard == MOVE_FOLDER) {
1636         delete m_clipboardFolderInfo;
1637     }
1638     m_clipboardFolderInfo = nullptr;
1639 
1640     if (m_clipboard == MOVE_FILE) {
1641         delete m_clipboardEntryInfo;
1642     }
1643     m_clipboardEntryInfo = nullptr;
1644 
1645     m_clipboard = 0;
1646 }
1647 
1648 static QStringList extractLayout(QTreeWidget *tree, QTreeWidgetItem *parent)
1649 {
1650     QStringList layout;
1651     if (!parent && !tree) {
1652         return layout;
1653     }
1654 
1655     bool firstFolder = true;
1656     bool firstEntry = true;
1657     int max = parent ? parent->childCount() : tree->topLevelItemCount();
1658     for (int i = 0; i < max; ++i) {
1659         TreeItem *item = dynamic_cast<TreeItem *>(parent ? parent->child(i) : tree->topLevelItem(i));
1660         if (!item) {
1661             continue;
1662         }
1663 
1664         if (item->isDirectory()) {
1665             if (firstFolder) {
1666                 firstFolder = false;
1667                 layout << QStringLiteral(":M"); // Add new folders here...
1668             }
1669             layout << (item->folderInfo()->id);
1670         } else if (item->isEntry()) {
1671             if (firstEntry) {
1672                 firstEntry = false;
1673                 layout << QStringLiteral(":F"); // Add new entries here...
1674             }
1675             layout << (item->entryInfo()->menuId());
1676         } else {
1677             layout << QStringLiteral(":S");
1678         }
1679     }
1680 
1681     return layout;
1682 }
1683 
1684 void TreeItem::saveLayout(MenuFile *menuFile)
1685 {
1686     if (m_layoutDirty) {
1687         QStringList layout = extractLayout(nullptr, this);
1688         menuFile->setLayout(folderInfo()->fullId, layout);
1689         m_layoutDirty = false;
1690     }
1691 
1692     for (int i = 0; i < childCount(); ++i) {
1693         TreeItem *item = dynamic_cast<TreeItem *>(child(i));
1694         if (item) {
1695             item->saveLayout(menuFile);
1696         }
1697     }
1698 }
1699 
1700 void TreeView::saveLayout()
1701 {
1702     if (m_layoutDirty) {
1703         QStringList layout = extractLayout(this, nullptr);
1704         m_menuFile->setLayout(m_rootFolder->fullId, layout);
1705         m_layoutDirty = false;
1706     }
1707 
1708     for (int i = 0; i < topLevelItemCount(); ++i) {
1709         TreeItem *item = dynamic_cast<TreeItem *>(topLevelItem(i));
1710         if (item) {
1711             item->saveLayout(m_menuFile);
1712         }
1713     }
1714 }
1715 
1716 bool TreeView::save()
1717 {
1718     saveLayout();
1719     m_rootFolder->save(m_menuFile);
1720 
1721     bool success = m_menuFile->performAllActions();
1722 
1723     m_newMenuIds.clear();
1724     m_newDirectoryList.clear();
1725 
1726     if (success) {
1727         KBuildSycocaProgressDialog::rebuildKSycoca(this);
1728     } else {
1729         KMessageBox::error(this,
1730                            QStringLiteral("<qt>") + i18n("Menu changes could not be saved because of the following problem:") + QStringLiteral("<br><br>")
1731                                + m_menuFile->error() + QStringLiteral("</qt>"));
1732     }
1733 
1734     sendReloadMenu();
1735 
1736     return success;
1737 }
1738 
1739 void TreeView::setLayoutDirty(TreeItem *parentItem)
1740 {
1741     if (parentItem) {
1742         parentItem->setLayoutDirty();
1743     } else {
1744         m_layoutDirty = true;
1745     }
1746 }
1747 
1748 bool TreeView::isLayoutDirty()
1749 {
1750     for (int i = 0; i < topLevelItemCount(); ++i) {
1751         TreeItem *item = dynamic_cast<TreeItem *>(topLevelItem(i));
1752         if (!item) {
1753             continue;
1754         }
1755 
1756         if (item->isLayoutDirty()) {
1757             return true;
1758         }
1759     }
1760 
1761     return false;
1762 }
1763 
1764 bool TreeView::dirty()
1765 {
1766     return m_layoutDirty || m_rootFolder->hasDirt() || m_menuFile->dirty() || isLayoutDirty();
1767 }
1768 
1769 void TreeView::findServiceShortcut(const QKeySequence &cut, KService::Ptr &service)
1770 {
1771     service = m_rootFolder->findServiceShortcut(cut);
1772 }
1773 
1774 void TreeView::restoreMenuSystem()
1775 {
1776     if (KMessageBox::questionTwoActions(this,
1777                                         i18n("Do you want to restore the system menu? Warning: This will remove all custom menus."),
1778                                         i18nc("@title:window", "Restore Menu System"),
1779                                         KStandardGuiItem::reset(),
1780                                         KStandardGuiItem::cancel())
1781         == KMessageBox::SecondaryAction) {
1782         return;
1783     }
1784     const QString kmenueditfile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/menus/applications-kmenuedit.menu");
1785     if (QFile::exists(kmenueditfile)) {
1786         if (!QFile::remove(kmenueditfile)) {
1787             qCWarning(KMENUEDIT_LOG) << "Could not delete " << kmenueditfile;
1788         }
1789     }
1790 
1791     const QString xdgappsdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/applications");
1792     if (QFileInfo(xdgappsdir).isDir()) {
1793         if (!QDir(xdgappsdir).removeRecursively()) {
1794             qCWarning(KMENUEDIT_LOG) << "Could not delete dir :" << xdgappsdir;
1795         }
1796     }
1797     const QString xdgdesktopdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/desktop-directories");
1798     if (QFileInfo(xdgdesktopdir).isDir()) {
1799         if (!QDir(xdgdesktopdir).removeRecursively()) {
1800             qCWarning(KMENUEDIT_LOG) << "Could not delete dir :" << xdgdesktopdir;
1801         }
1802     }
1803 
1804     KBuildSycocaProgressDialog::rebuildKSycoca(this);
1805     clear();
1806     cleanupClipboard();
1807     delete m_rootFolder;
1808     delete m_separator;
1809 
1810     m_layoutDirty = false;
1811     m_newMenuIds.clear();
1812     m_newDirectoryList.clear();
1813     m_menuFile->restoreMenuSystem(kmenueditfile);
1814 
1815     m_rootFolder = new MenuFolderInfo;
1816     m_separator = new MenuSeparatorInfo;
1817 
1818     readMenuFolderInfo();
1819     fill();
1820     sendReloadMenu();
1821     emit disableAction();
1822     emit entrySelected((MenuEntryInfo *)nullptr);
1823 }
1824 
1825 void TreeView::updateTreeView(bool showHidden)
1826 {
1827     m_showHidden = showHidden;
1828     clear();
1829     cleanupClipboard();
1830     delete m_rootFolder;
1831     delete m_separator;
1832 
1833     m_layoutDirty = false;
1834     m_newMenuIds.clear();
1835     m_newDirectoryList.clear();
1836 
1837     m_rootFolder = new MenuFolderInfo;
1838     m_separator = new MenuSeparatorInfo;
1839 
1840     readMenuFolderInfo();
1841     fill();
1842     sendReloadMenu();
1843     emit disableAction();
1844     emit entrySelected((MenuEntryInfo *)nullptr);
1845 }
1846 
1847 void TreeView::sendReloadMenu()
1848 {
1849     QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/kickoff"), QStringLiteral("org.kde.plasma"), QStringLiteral("reloadMenu"));
1850     QDBusConnection::sessionBus().send(message);
1851 }
1852 
1853 // Slot to expand or retract items in tree based on length of search string
1854 void TreeView::searchUpdated(const QString &searchString)
1855 {
1856     // expand all categories if we typed more than a few characters, otherwise collapse and un-select everything
1857     // use logicalLength for CJK users
1858     if (KStringHandler::logicalLength(searchString) > 2) {
1859         expandAll();
1860     } else {
1861         collapseAll();
1862         setCurrentIndex(rootIndex());
1863     }
1864 }
1865 
1866 MenuItemMimeData::MenuItemMimeData(TreeItem *item)
1867     : QMimeData()
1868     , m_item(item)
1869 {
1870 }
1871 
1872 TreeItem *MenuItemMimeData::item() const
1873 {
1874     return m_item;
1875 }
1876 
1877 QStringList MenuItemMimeData::formats() const
1878 {
1879     QStringList formats;
1880     if (!m_item) {
1881         return formats;
1882     }
1883 
1884     formats << QLatin1String(s_internalMimeType);
1885     return formats;
1886 }
1887 
1888 bool MenuItemMimeData::hasFormat(const QString &mimeType) const
1889 {
1890     return m_item && mimeType == QLatin1String(s_internalMimeType);
1891 }
1892 
1893 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
1894 QVariant MenuItemMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const
1895 #else
1896 QVariant MenuItemMimeData::retrieveData(const QString &mimeType, QMetaType type) const
1897 #endif
1898 {
1899     Q_UNUSED(type);
1900 
1901     if (m_item && mimeType == QLatin1String(s_internalMimeType)) {
1902         return QVariant::fromValue<TreeItem *>(m_item);
1903     }
1904 
1905     return QVariant();
1906 }