File indexing completed on 2024-04-21 05:27:20

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 QMimeData *TreeView::mimeData(const QList<QTreeWidgetItem *> &items) const
0708 {
0709     if (items.isEmpty()) {
0710         return nullptr;
0711     }
0712 
0713     return new MenuItemMimeData(dynamic_cast<TreeItem *>(items.first()));
0714 }
0715 
0716 static QString createDesktopFile(const QString &file, QString *menuId, QStringList *excludeList)
0717 {
0718     QString base = file.mid(file.lastIndexOf(QLatin1Char('/')) + 1);
0719     base = base.left(base.lastIndexOf(QLatin1Char('.')));
0720 
0721     const QRegularExpression re(QStringLiteral("(.*)(?=-\\d+)"));
0722     const QRegularExpressionMatch match = re.match(base);
0723     base = match.hasMatch() ? match.captured(1) : base;
0724 
0725     QString result = KService::newServicePath(true, base, menuId, excludeList);
0726     excludeList->append(*menuId);
0727     // Todo for Undo-support: Undo menuId allocation:
0728 
0729     return result;
0730 }
0731 
0732 static KDesktopFile *copyDesktopFile(MenuEntryInfo *entryInfo, QString *menuId, QStringList *excludeList)
0733 {
0734     QString result = createDesktopFile(entryInfo->file(), menuId, excludeList);
0735     KDesktopFile *df = entryInfo->desktopFile()->copyTo(result);
0736     df->desktopGroup().deleteEntry("Categories"); // Don't set any categories!
0737 
0738     return df;
0739 }
0740 
0741 static QString createDirectoryFile(const QString &file, QStringList *excludeList)
0742 {
0743     QString base = file.mid(file.lastIndexOf(QLatin1Char('/')) + 1);
0744     base = base.left(base.lastIndexOf(QLatin1Char('.')));
0745 
0746     QString result;
0747     int i = 1;
0748     while (true) {
0749         if (i == 1) {
0750             result = base + QStringLiteral(".directory");
0751         } else {
0752             result = base + QStringLiteral("-%1.directory").arg(i);
0753         }
0754 
0755         if (!excludeList->contains(result)) {
0756             if (QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("desktop-directories/") + result).isEmpty()) {
0757                 break;
0758             }
0759         }
0760         i++;
0761     }
0762     excludeList->append(result);
0763     result = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/desktop-directories/") + result;
0764     return result;
0765 }
0766 
0767 bool TreeView::dropMimeData(QTreeWidgetItem *item, int index, const QMimeData *data, Qt::DropAction action)
0768 {
0769     // get destination folder
0770     TreeItem *titem = item ? dynamic_cast<TreeItem *>(item) : nullptr;
0771     if (item && !titem) {
0772         return false;
0773     }
0774 
0775     TreeItem *parentItem = nullptr;
0776     QTreeWidgetItem *after = titem;
0777     // find the parent item and which item the dropped item should go after
0778     if (titem) {
0779         if (titem->isDirectory()) {
0780             parentItem = titem;
0781             after = titem->child(index);
0782             if (!after) {
0783                 after = titem->child(titem->childCount() - 1);
0784             }
0785         } else {
0786             parentItem = dynamic_cast<TreeItem *>(titem->parent());
0787             if (titem->parent() && !parentItem) {
0788                 return false;
0789             }
0790         }
0791     } else if (index > 0) {
0792         after = topLevelItem(index);
0793         if (!after) {
0794             after = topLevelItem(topLevelItemCount() - 1);
0795         }
0796     }
0797 
0798     QString folder = parentItem ? parentItem->directory() : QStringLiteral("/");
0799     MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
0800     // qCDebug(KMENUEDIT_LOG) << "think we're dropping on" << (parentItem ? parentItem->text(0) : "Top Level") <<  index;
0801 
0802     if (!data->hasFormat(QLatin1String(s_internalMimeType))) {
0803         // External drop
0804         if (!data->hasUrls()) {
0805             return false;
0806         }
0807 
0808         QList<QUrl> urls = KUrlMimeData::urlsFromMimeData(data);
0809         if (urls.isEmpty() || !urls[0].isLocalFile()) {
0810             return false;
0811         }
0812 
0813         // FIXME: this should really support multiple DnD
0814         QString path = urls[0].path();
0815         if (!path.endsWith(QLatin1String(".desktop"))) {
0816             return false;
0817         }
0818 
0819         QString menuId;
0820         QString result = createDesktopFile(path, &menuId, &m_newMenuIds);
0821         KDesktopFile orig_df(path);
0822         KDesktopFile *df = orig_df.copyTo(result);
0823         df->desktopGroup().deleteEntry("Categories"); // Don't set any categories!
0824 
0825         KService::Ptr s(new KService(df));
0826         s->setMenuId(menuId);
0827 
0828         MenuEntryInfo *entryInfo = new MenuEntryInfo(s, df);
0829 
0830         QString oldCaption = entryInfo->caption;
0831         QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption);
0832         entryInfo->setCaption(newCaption);
0833 
0834         // Add file to menu
0835         // m_menuFile->addEntry(folder, menuId);
0836         m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
0837 
0838         // create the TreeItem
0839         if (parentItem) {
0840             parentItem->setExpanded(true);
0841         }
0842 
0843         // update fileInfo data
0844         parentFolderInfo->add(entryInfo);
0845 
0846         TreeItem *newItem = createTreeItem(parentItem, after, entryInfo, true);
0847         setCurrentItem(newItem);
0848 
0849         setLayoutDirty(parentItem);
0850         return true;
0851     }
0852 
0853     QVariant p(data->data(QLatin1String(s_internalMimeType)));
0854     const MenuItemMimeData *itemData = dynamic_cast<const MenuItemMimeData *>(data);
0855     if (!itemData) {
0856         return false;
0857     }
0858 
0859     TreeItem *dragItem = itemData->item();
0860     if (!dragItem || dragItem == after) {
0861         return false; // Nothing to do
0862     }
0863 
0864     ////qCDebug(KMENUEDIT_LOG) << "an internal drag of" << dragItem->text(0) << (parentItem ? parentItem->text(0) : "Top level");
0865     if (dragItem->isDirectory()) {
0866         MenuFolderInfo *folderInfo = dragItem->folderInfo();
0867         if (action == Qt::CopyAction) {
0868             // FIXME:
0869             // * Create new .directory file
0870         } else {
0871             TreeItem *tmpItem = static_cast<TreeItem *>(parentItem);
0872             while (tmpItem) {
0873                 if (tmpItem == dragItem) {
0874                     return false;
0875                 }
0876 
0877                 tmpItem = static_cast<TreeItem *>(tmpItem->parent());
0878             }
0879 
0880             // Remove MenuFolderInfo
0881             TreeItem *oldParentItem = static_cast<TreeItem *>(dragItem->parent());
0882             MenuFolderInfo *oldParentFolderInfo = oldParentItem ? oldParentItem->folderInfo() : m_rootFolder;
0883             oldParentFolderInfo->take(folderInfo);
0884 
0885             // Move menu
0886             QString oldFolder = folderInfo->fullId;
0887             QString folderName = folderInfo->id;
0888             QString newFolder = m_menuFile->uniqueMenuName(folder, folderName, parentFolderInfo->existingMenuIds());
0889             folderInfo->id = newFolder;
0890 
0891             // Add file to menu
0892             // m_menuFile->moveMenu(oldFolder, folder + newFolder);
0893             // qCDebug(KMENUEDIT_LOG) << "moving" << dragItem->text(0) << "to" << folder + newFolder;
0894             m_menuFile->pushAction(MenuFile::MOVE_MENU, oldFolder, folder + newFolder);
0895 
0896             // Make sure caption is unique
0897             QString newCaption = parentFolderInfo->uniqueMenuCaption(folderInfo->caption);
0898             if (newCaption != folderInfo->caption) {
0899                 folderInfo->setCaption(newCaption);
0900             }
0901 
0902             // create the TreeItem
0903             if (parentItem) {
0904                 parentItem->setExpanded(true);
0905             }
0906 
0907             // update fileInfo data
0908             folderInfo->updateFullId(parentFolderInfo->fullId);
0909             folderInfo->setInUse(true);
0910             parentFolderInfo->add(folderInfo);
0911 
0912             if (parentItem != oldParentItem) {
0913                 if (oldParentItem) {
0914                     oldParentItem->takeChild(oldParentItem->indexOfChild(dragItem));
0915                 } else {
0916                     takeTopLevelItem(indexOfTopLevelItem(dragItem));
0917                 }
0918             }
0919 
0920             if (parentItem) {
0921                 parentItem->insertChild(after ? parentItem->indexOfChild(after) + 1 : parentItem->childCount(), dragItem);
0922             } else {
0923                 insertTopLevelItem(after ? indexOfTopLevelItem(after) : topLevelItemCount(), dragItem);
0924             }
0925 
0926             dragItem->setName(folderInfo->caption);
0927             dragItem->setDirectoryPath(folderInfo->fullId);
0928             setCurrentItem(dragItem);
0929         }
0930     } else if (dragItem->isEntry()) {
0931         MenuEntryInfo *entryInfo = dragItem->entryInfo();
0932         QString menuId = entryInfo->menuId();
0933 
0934         if (action == Qt::CopyAction) {
0935             // Need to copy file and then add it
0936             KDesktopFile *df = copyDesktopFile(entryInfo, &menuId, &m_newMenuIds); // Duplicate
0937             // UNDO-ACTION: NEW_MENU_ID (menuId)
0938 
0939             KService::Ptr s(new KService(df));
0940             s->setMenuId(menuId);
0941 
0942             entryInfo = new MenuEntryInfo(s, df);
0943 
0944             QString oldCaption = entryInfo->caption;
0945             QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption);
0946             entryInfo->setCaption(newCaption);
0947         } else {
0948             del(dragItem, false);
0949             QString oldCaption = entryInfo->caption;
0950             QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption);
0951             entryInfo->setCaption(newCaption);
0952             entryInfo->setInUse(true);
0953         }
0954 
0955         // Add file to menu
0956         // m_menuFile->addEntry(folder, menuId);
0957         m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
0958 
0959         // create the TreeItem
0960         if (parentItem) {
0961             parentItem->setExpanded(true);
0962         }
0963 
0964         // update fileInfo data
0965         parentFolderInfo->add(entryInfo);
0966 
0967         TreeItem *newItem = createTreeItem(parentItem, after, entryInfo);
0968         setCurrentItem(newItem);
0969 
0970         // clipboard no longer owns entryInfo
0971         if (m_clipboardEntryInfo == entryInfo) {
0972             m_clipboard = COPY_FILE;
0973         }
0974     } else {
0975         // copying a separator
0976         if (action != Qt::CopyAction) {
0977             del(dragItem, false);
0978         }
0979 
0980         TreeItem *newItem = createTreeItem(parentItem, after, m_separator);
0981         setCurrentItem(newItem);
0982     }
0983 
0984     // qCDebug(KMENUEDIT_LOG) << "setting the layout to be dirty at" << parentItem;
0985     setLayoutDirty(parentItem);
0986     return true;
0987 }
0988 
0989 QTreeWidgetItem *TreeView::selectedItem()
0990 {
0991     QList<QTreeWidgetItem *> selection = selectedItems();
0992 
0993     if (selection.isEmpty()) {
0994         return nullptr;
0995     }
0996 
0997     return selection.first();
0998 }
0999 
1000 void TreeView::contextMenuEvent(QContextMenuEvent *event)
1001 {
1002     if (m_popupMenu && itemAt(event->pos())) {
1003         m_popupMenu->exec(event->globalPos());
1004     }
1005 }
1006 
1007 void TreeView::dropEvent(QDropEvent *event)
1008 {
1009     // this prevents QTreeWidget from interfering with our moves
1010     QTreeView::dropEvent(event);
1011 }
1012 
1013 void TreeView::newsubmenu()
1014 {
1015     TreeItem *parentItem = nullptr;
1016     TreeItem *item = (TreeItem *)selectedItem();
1017 
1018     bool ok;
1019     QString caption = QInputDialog::getText(this, i18n("New Submenu"), i18n("Submenu name:"), QLineEdit::Normal, QString(), &ok);
1020 
1021     if (!ok) {
1022         return;
1023     }
1024 
1025     QString file = caption;
1026     file.replace(QLatin1Char('/'), QLatin1Char('-'));
1027 
1028     file = createDirectoryFile(file, &m_newDirectoryList); // Create
1029 
1030     // get destination folder
1031     QString folder;
1032 
1033     if (!item) {
1034         parentItem = nullptr;
1035         folder.clear();
1036     } else if (item->isDirectory()) {
1037         parentItem = item;
1038         item = nullptr;
1039         folder = parentItem->directory();
1040     } else {
1041         parentItem = static_cast<TreeItem *>(item->parent());
1042         folder = parentItem ? parentItem->directory() : QString();
1043     }
1044 
1045     MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1046     MenuFolderInfo *folderInfo = new MenuFolderInfo();
1047     folderInfo->caption = parentFolderInfo->uniqueMenuCaption(caption);
1048     folderInfo->id = m_menuFile->uniqueMenuName(folder, caption, parentFolderInfo->existingMenuIds());
1049     folderInfo->directoryFile = file;
1050     folderInfo->icon = QStringLiteral("package");
1051     folderInfo->hidden = false;
1052     folderInfo->setDirty();
1053 
1054     KDesktopFile *df = new KDesktopFile(file);
1055     KConfigGroup desktopGroup = df->desktopGroup();
1056     desktopGroup.writeEntry("Name", folderInfo->caption);
1057     desktopGroup.writeEntry("Icon", folderInfo->icon);
1058     df->sync();
1059     delete df;
1060     // Add file to menu
1061     // m_menuFile->addMenu(folder + folderInfo->id, file);
1062     m_menuFile->pushAction(MenuFile::ADD_MENU, folder + folderInfo->id, file);
1063 
1064     folderInfo->fullId = parentFolderInfo->fullId + folderInfo->id;
1065 
1066     // create the TreeItem
1067     if (parentItem) {
1068         parentItem->setExpanded(true);
1069     }
1070 
1071     // update fileInfo data
1072     parentFolderInfo->add(folderInfo);
1073 
1074     TreeItem *newItem = createTreeItem(parentItem, item, folderInfo, true);
1075 
1076     setCurrentItem(newItem);
1077     setLayoutDirty(parentItem);
1078 }
1079 
1080 void TreeView::newitem()
1081 {
1082     TreeItem *parentItem = nullptr;
1083     TreeItem *item = (TreeItem *)selectedItem();
1084 
1085     bool ok;
1086     QString caption = QInputDialog::getText(this, i18n("New Item"), i18n("Item name:"), QLineEdit::Normal, QString(), &ok);
1087 
1088     if (!ok) {
1089         return;
1090     }
1091 
1092     QString menuId;
1093     QString file = caption;
1094     file.replace(QLatin1Char('/'), QLatin1Char('-'));
1095 
1096     file = createDesktopFile(file, &menuId, &m_newMenuIds); // Create
1097 
1098     KDesktopFile *df = new KDesktopFile(file);
1099     KConfigGroup desktopGroup = df->desktopGroup();
1100     desktopGroup.writeEntry("Name", caption);
1101     desktopGroup.writeEntry("Type", "Application");
1102 
1103     // get destination folder
1104     QString folder;
1105 
1106     if (!item) {
1107         parentItem = nullptr;
1108         folder.clear();
1109     } else if (item->isDirectory()) {
1110         parentItem = item;
1111         item = nullptr;
1112         folder = parentItem->directory();
1113     } else {
1114         parentItem = static_cast<TreeItem *>(item->parent());
1115         folder = parentItem ? parentItem->directory() : QString();
1116     }
1117 
1118     MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1119 
1120     // Add file to menu
1121     // m_menuFile->addEntry(folder, menuId);
1122     m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
1123 
1124     KService::Ptr s(new KService(df));
1125     s->setMenuId(menuId);
1126 
1127     MenuEntryInfo *entryInfo = new MenuEntryInfo(s, df);
1128 
1129     // create the TreeItem
1130     if (parentItem) {
1131         parentItem->setExpanded(true);
1132     }
1133 
1134     // update fileInfo data
1135     parentFolderInfo->add(entryInfo);
1136 
1137     TreeItem *newItem = createTreeItem(parentItem, item, entryInfo, true);
1138 
1139     setCurrentItem(newItem);
1140     setLayoutDirty(parentItem);
1141 }
1142 
1143 void TreeView::newsep()
1144 {
1145     TreeItem *parentItem = nullptr;
1146     TreeItem *item = (TreeItem *)selectedItem();
1147 
1148     if (!item) {
1149         parentItem = nullptr;
1150     } else if (item->isDirectory()) {
1151         parentItem = item;
1152         item = nullptr;
1153     } else {
1154         parentItem = static_cast<TreeItem *>(item->parent());
1155     }
1156 
1157     // create the TreeItem
1158     if (parentItem) {
1159         parentItem->setExpanded(true);
1160     }
1161 
1162     TreeItem *newItem = createTreeItem(parentItem, item, m_separator, true);
1163 
1164     setCurrentItem(newItem);
1165     setLayoutDirty(parentItem);
1166 }
1167 
1168 void TreeView::cut()
1169 {
1170     copy(true);
1171 
1172     // Select new current item
1173     // TODO: is this completely redundant?
1174     setCurrentItem(currentItem());
1175 }
1176 
1177 void TreeView::copy()
1178 {
1179     copy(false);
1180 }
1181 
1182 void TreeView::copy(bool cutting)
1183 {
1184     TreeItem *item = (TreeItem *)selectedItem();
1185 
1186     // nil selected? -> nil to copy
1187     if (item == nullptr) {
1188         return;
1189     }
1190 
1191     if (cutting) {
1192         setLayoutDirty((TreeItem *)item->parent());
1193     }
1194 
1195     // clean up old stuff
1196     cleanupClipboard();
1197 
1198     // is item a folder or a file?
1199     if (item->isDirectory()) {
1200         QString folder = item->directory();
1201         if (cutting) {
1202             // Place in clipboard
1203             m_clipboard = MOVE_FOLDER;
1204             m_clipboardFolderInfo = item->folderInfo();
1205 
1206             del(item, false);
1207         } else {
1208             // Place in clipboard
1209             m_clipboard = COPY_FOLDER;
1210             m_clipboardFolderInfo = item->folderInfo();
1211         }
1212     } else if (item->isEntry()) {
1213         if (cutting) {
1214             // Place in clipboard
1215             m_clipboard = MOVE_FILE;
1216             m_clipboardEntryInfo = item->entryInfo();
1217 
1218             del(item, false);
1219         } else {
1220             // Place in clipboard
1221             m_clipboard = COPY_FILE;
1222             m_clipboardEntryInfo = item->entryInfo();
1223         }
1224     } else {
1225         // Place in clipboard
1226         m_clipboard = COPY_SEPARATOR;
1227         if (cutting) {
1228             del(item, false);
1229         }
1230     }
1231 
1232     m_ac->action(PASTE_ACTION_NAME)->setEnabled(true);
1233 }
1234 
1235 void TreeView::paste()
1236 {
1237     TreeItem *parentItem = nullptr;
1238     TreeItem *item = (TreeItem *)selectedItem();
1239 
1240     // nil selected? -> nil to paste to
1241     if (item == nullptr) {
1242         return;
1243     }
1244 
1245     // is there content in the clipboard?
1246     if (!m_clipboard) {
1247         return;
1248     }
1249 
1250     // get destination folder
1251     QString folder;
1252 
1253     if (item->isDirectory()) {
1254         parentItem = item;
1255         item = nullptr;
1256         folder = parentItem->directory();
1257     } else {
1258         parentItem = static_cast<TreeItem *>(item->parent());
1259         folder = parentItem ? parentItem->directory() : QString();
1260     }
1261 
1262     MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1263     int command = m_clipboard;
1264     if ((command == COPY_FOLDER) || (command == MOVE_FOLDER)) {
1265         MenuFolderInfo *folderInfo = m_clipboardFolderInfo;
1266         if (command == COPY_FOLDER) {
1267             // Ugh.. this is hard :)
1268             // * Create new .directory file
1269             // Add
1270         } else if (command == MOVE_FOLDER) {
1271             // Move menu
1272             QString oldFolder = folderInfo->fullId;
1273             QString folderName = folderInfo->id;
1274             QString newFolder = m_menuFile->uniqueMenuName(folder, folderName, parentFolderInfo->existingMenuIds());
1275             folderInfo->id = newFolder;
1276 
1277             // Add file to menu
1278             // m_menuFile->moveMenu(oldFolder, folder + newFolder);
1279             m_menuFile->pushAction(MenuFile::MOVE_MENU, oldFolder, folder + newFolder);
1280 
1281             // Make sure caption is unique
1282             QString newCaption = parentFolderInfo->uniqueMenuCaption(folderInfo->caption);
1283             if (newCaption != folderInfo->caption) {
1284                 folderInfo->setCaption(newCaption);
1285             }
1286             // create the TreeItem
1287             if (parentItem) {
1288                 parentItem->setExpanded(true);
1289             }
1290 
1291             // update fileInfo data
1292             folderInfo->fullId = parentFolderInfo->fullId + folderInfo->id;
1293             folderInfo->setInUse(true);
1294             parentFolderInfo->add(folderInfo);
1295 
1296             TreeItem *newItem = createTreeItem(parentItem, item, folderInfo);
1297 
1298             setCurrentItem(newItem);
1299         }
1300 
1301         m_clipboard = COPY_FOLDER; // Next one copies.
1302     } else if ((command == COPY_FILE) || (command == MOVE_FILE)) {
1303         MenuEntryInfo *entryInfo = m_clipboardEntryInfo;
1304         QString menuId;
1305 
1306         if (command == COPY_FILE) {
1307             // Need to copy file and then add it
1308             KDesktopFile *df = copyDesktopFile(entryInfo, &menuId, &m_newMenuIds); // Duplicate
1309 
1310             KService::Ptr s(new KService(df));
1311             s->setMenuId(menuId);
1312             entryInfo = new MenuEntryInfo(s, df);
1313 
1314             QString oldCaption = entryInfo->caption;
1315             QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption);
1316             entryInfo->setCaption(newCaption);
1317         } else if (command == MOVE_FILE) {
1318             menuId = entryInfo->menuId();
1319             m_clipboard = COPY_FILE; // Next one copies.
1320 
1321             QString oldCaption = entryInfo->caption;
1322             QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption);
1323             entryInfo->setCaption(newCaption);
1324             entryInfo->setInUse(true);
1325         }
1326         // Add file to menu
1327         // m_menuFile->addEntry(folder, menuId);
1328         m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
1329 
1330         // create the TreeItem
1331         if (parentItem) {
1332             parentItem->setExpanded(true);
1333         }
1334 
1335         // update fileInfo data
1336         parentFolderInfo->add(entryInfo);
1337 
1338         TreeItem *newItem = createTreeItem(parentItem, item, entryInfo, true);
1339 
1340         setCurrentItem(newItem);
1341     } else {
1342         // create separator
1343         if (parentItem) {
1344             parentItem->setExpanded(true);
1345         }
1346 
1347         TreeItem *newItem = createTreeItem(parentItem, item, m_separator, true);
1348 
1349         setCurrentItem(newItem);
1350     }
1351     setLayoutDirty(parentItem);
1352 }
1353 
1354 /**
1355  * This slot is called from the signal mapper to sort children contained in an item.
1356  * This item is determined according to the chosen sort type.
1357  *
1358  * @brief Determine which item is to sort, and do it.
1359  * @param sortCmd Sort type.
1360  */
1361 void TreeView::sort(const int sortCmd)
1362 {
1363     // determine the chosen sort type and the selected item
1364     SortType sortType = static_cast<SortType>(sortCmd);
1365     TreeItem *itemToSort;
1366     if (sortType == SortByName || sortType == SortByDescription) {
1367         itemToSort = static_cast<TreeItem *>(selectedItem());
1368         sortItem(itemToSort, sortType);
1369     } else if (sortType == SortAllByDescription) {
1370         sortType = SortByDescription;
1371         for (int i = 0; i < topLevelItemCount(); ++i) {
1372             itemToSort = static_cast<TreeItem *>(topLevelItem(i));
1373             sortItem(itemToSort, sortType);
1374         }
1375     } else { /* if (sortType == SortAllByName) */
1376         sortType = SortByName;
1377         for (int i = 0; i < topLevelItemCount(); ++i) {
1378             itemToSort = static_cast<TreeItem *>(topLevelItem(i));
1379             sortItem(itemToSort, sortType);
1380         }
1381     }
1382 }
1383 
1384 /**
1385  * Sort children of the given item, according to the sort type.
1386  * The sorting is done on children groups, splited by separator items.
1387  *
1388  * @brief Sort item children.
1389  * @param item Item to sort.
1390  * @param sortType Sort type.
1391  */
1392 void TreeView::sortItem(TreeItem *item, SortType sortType)
1393 {
1394     // sort the selected item only if contains children
1395     if ((!item->isDirectory()) || (item->childCount() == 0)) {
1396         return;
1397     }
1398 
1399     // remove contained children
1400     QList<QTreeWidgetItem *> children = item->takeChildren();
1401 
1402     // sort children groups, splited by separator items
1403     QList<QTreeWidgetItem *>::iterator startIt = children.begin();
1404     QList<QTreeWidgetItem *>::iterator currentIt = children.begin();
1405     while (currentIt != children.end()) {
1406         TreeItem *child = static_cast<TreeItem *>(*currentIt);
1407         // if it's a separator, sort previous items and continue on following items
1408         if (child->isSeparator() && startIt != currentIt) {
1409             sortItemChildren(startIt, currentIt, sortType);
1410             startIt = currentIt + 1;
1411         }
1412         ++currentIt;
1413     }
1414     sortItemChildren(startIt, currentIt, sortType);
1415 
1416     // insert sorted children in the tree
1417     item->addChildren(children);
1418     foreach (QTreeWidgetItem *child, children) {
1419         // recreate item widget for separators
1420         TreeItem *treeItem = static_cast<TreeItem *>(child);
1421         if (treeItem->isSeparator()) {
1422             setItemWidget(treeItem, 0, new SeparatorWidget);
1423         }
1424 
1425         // try to sort sub-children
1426         sortItem(static_cast<TreeItem *>(child), sortType);
1427     }
1428 
1429     // flag current item as dirty
1430     TreeItem *itemToFlagAsDirty = item;
1431     // if tree root item, set the entire layout as dirty
1432     if (item == invisibleRootItem()) {
1433         itemToFlagAsDirty = nullptr;
1434     }
1435     setLayoutDirty(itemToFlagAsDirty);
1436 }
1437 
1438 /**
1439  * Sort a children range defined with two list iterators, according to the sort type.
1440  *
1441  * @brief Sort a children range.
1442  * @param begin First child iterator.
1443  * @param end Last child iterator (exclusive, pointed child won't be affected).
1444  * @param sortType Sort type.
1445  */
1446 void TreeView::sortItemChildren(const QList<QTreeWidgetItem *>::iterator &begin, const QList<QTreeWidgetItem *>::iterator &end, SortType sortType)
1447 {
1448     // sort by name
1449     if (sortType == SortByName) {
1450         std::sort(begin, end, TreeItem::itemNameLessThan);
1451     }
1452     // sort by description
1453     else if (sortType == SortByDescription) {
1454         std::sort(begin, end, TreeItem::itemDescriptionLessThan);
1455     }
1456 }
1457 
1458 /**
1459  * @brief Move up the selected item.
1460  */
1461 void TreeView::moveUpItem()
1462 {
1463     moveUpOrDownItem(true);
1464 }
1465 
1466 /**
1467  * @brief Move down the selected item.
1468  */
1469 void TreeView::moveDownItem()
1470 {
1471     moveUpOrDownItem(false);
1472 }
1473 
1474 /**
1475  * Move the selected item on desired direction (up or down).
1476  *
1477  * @brief Move up/down the selected item.
1478  * @param isMovingUpAction True to move up, false to move down.
1479  */
1480 void TreeView::moveUpOrDownItem(bool isMovingUpAction)
1481 {
1482     // get the selected item and its parent
1483     TreeItem *sourceItem = static_cast<TreeItem *>(selectedItem());
1484     if (!sourceItem) {
1485         return;
1486     }
1487     TreeItem *parentItem = getParentItem(sourceItem);
1488 
1489     // get selected item index
1490     int sourceItemIndex = parentItem->indexOfChild(sourceItem);
1491 
1492     // find the second item to swap
1493     TreeItem *destItem = nullptr;
1494     int destIndex;
1495     if (isMovingUpAction) {
1496         destIndex = sourceItemIndex - 1;
1497         destItem = static_cast<TreeItem *>(parentItem->child(destIndex));
1498     } else {
1499         destIndex = sourceItemIndex + 1;
1500         destItem = static_cast<TreeItem *>(parentItem->child(destIndex));
1501     }
1502 
1503     // swap items
1504     parentItem->removeChild(sourceItem);
1505     parentItem->insertChild(destIndex, sourceItem);
1506 
1507     // recreate item widget for separators
1508     if (sourceItem->isSeparator()) {
1509         setItemWidget(sourceItem, 0, new SeparatorWidget);
1510     }
1511     if (destItem->isSeparator()) {
1512         setItemWidget(destItem, 0, new SeparatorWidget);
1513     }
1514 
1515     // set the focus on the source item
1516     setCurrentItem(sourceItem);
1517 
1518     // flag parent item as dirty (if the parent is the root item, set the entire layout as dirty)
1519     if (parentItem == invisibleRootItem()) {
1520         parentItem = nullptr;
1521     }
1522     setLayoutDirty(parentItem);
1523 }
1524 
1525 /**
1526  * For a given item, return its parent. For top items, return the invisible root item.
1527  *
1528  * @brief Get the parent item.
1529  * @param item Item.
1530  * @return Parent item.
1531  */
1532 TreeItem *TreeView::getParentItem(QTreeWidgetItem *item) const
1533 {
1534     QTreeWidgetItem *parentItem = item->parent();
1535     if (!parentItem) {
1536         parentItem = invisibleRootItem();
1537     }
1538     return static_cast<TreeItem *>(parentItem);
1539 }
1540 
1541 void TreeView::del()
1542 {
1543     TreeItem *item = (TreeItem *)selectedItem();
1544 
1545     // nil selected? -> nil to delete
1546     if (item == nullptr) {
1547         return;
1548     }
1549 
1550     del(item, true);
1551 
1552     // Select new current item
1553     // TODO: is this completely redundant?
1554     setCurrentItem(currentItem());
1555 }
1556 
1557 void TreeView::del(TreeItem *item, bool deleteInfo)
1558 {
1559     TreeItem *parentItem = static_cast<TreeItem *>(item->parent());
1560     // is file a .directory or a .desktop file
1561     if (item->isDirectory()) {
1562         if (KMessageBox::questionTwoActions(this,
1563                                             i18n("All submenus of '%1' will be removed. Do you want to continue?", item->name()),
1564                                             i18nc("@title:window", "Delete"),
1565                                             KStandardGuiItem::del(),
1566                                             KStandardGuiItem::cancel())
1567             == KMessageBox::SecondaryAction) {
1568             return;
1569         }
1570 
1571         MenuFolderInfo *folderInfo = item->folderInfo();
1572 
1573         // Remove MenuFolderInfo
1574         MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1575         parentFolderInfo->take(folderInfo);
1576         folderInfo->setInUse(false);
1577 
1578         if (m_clipboard == COPY_FOLDER && (m_clipboardFolderInfo == folderInfo)) {
1579             // Copy + Del == Cut
1580             m_clipboard = MOVE_FOLDER; // Clipboard now owns folderInfo
1581         } else {
1582             if (folderInfo->takeRecursive(m_clipboardFolderInfo)) {
1583                 m_clipboard = MOVE_FOLDER; // Clipboard now owns m_clipboardFolderInfo
1584             }
1585             if (deleteInfo) {
1586                 delete folderInfo; // Delete folderInfo
1587             }
1588         }
1589 
1590         // Remove from menu
1591         // m_menuFile->removeMenu(item->directory());
1592         m_menuFile->pushAction(MenuFile::REMOVE_MENU, item->directory(), QString());
1593 
1594         // Remove tree item
1595         delete item;
1596     } else if (item->isEntry()) {
1597         MenuEntryInfo *entryInfo = item->entryInfo();
1598         QString menuId = entryInfo->menuId();
1599 
1600         // Remove MenuFolderInfo
1601         MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1602         parentFolderInfo->take(entryInfo);
1603         entryInfo->setInUse(false);
1604 
1605         if (m_clipboard == COPY_FILE && (m_clipboardEntryInfo == entryInfo)) {
1606             // Copy + Del == Cut
1607             m_clipboard = MOVE_FILE; // Clipboard now owns entryInfo
1608         } else {
1609             if (deleteInfo) {
1610                 delete entryInfo; // Delete entryInfo
1611             }
1612         }
1613 
1614         // Remove from menu
1615         QString folder = parentItem ? parentItem->directory() : QString();
1616         // m_menuFile->removeEntry(folder, menuId);
1617         m_menuFile->pushAction(MenuFile::REMOVE_ENTRY, folder, menuId);
1618 
1619         // Remove tree item
1620         delete item;
1621     } else {
1622         // Remove separator
1623         delete item;
1624     }
1625 
1626     setLayoutDirty(parentItem);
1627 }
1628 
1629 void TreeView::cleanupClipboard()
1630 {
1631     if (m_clipboard == MOVE_FOLDER) {
1632         delete m_clipboardFolderInfo;
1633     }
1634     m_clipboardFolderInfo = nullptr;
1635 
1636     if (m_clipboard == MOVE_FILE) {
1637         delete m_clipboardEntryInfo;
1638     }
1639     m_clipboardEntryInfo = nullptr;
1640 
1641     m_clipboard = 0;
1642 }
1643 
1644 static QStringList extractLayout(QTreeWidget *tree, QTreeWidgetItem *parent)
1645 {
1646     QStringList layout;
1647     if (!parent && !tree) {
1648         return layout;
1649     }
1650 
1651     bool firstFolder = true;
1652     bool firstEntry = true;
1653     int max = parent ? parent->childCount() : tree->topLevelItemCount();
1654     for (int i = 0; i < max; ++i) {
1655         TreeItem *item = dynamic_cast<TreeItem *>(parent ? parent->child(i) : tree->topLevelItem(i));
1656         if (!item) {
1657             continue;
1658         }
1659 
1660         if (item->isDirectory()) {
1661             if (firstFolder) {
1662                 firstFolder = false;
1663                 layout << QStringLiteral(":M"); // Add new folders here...
1664             }
1665             layout << (item->folderInfo()->id);
1666         } else if (item->isEntry()) {
1667             if (firstEntry) {
1668                 firstEntry = false;
1669                 layout << QStringLiteral(":F"); // Add new entries here...
1670             }
1671             layout << (item->entryInfo()->menuId());
1672         } else {
1673             layout << QStringLiteral(":S");
1674         }
1675     }
1676 
1677     return layout;
1678 }
1679 
1680 void TreeItem::saveLayout(MenuFile *menuFile)
1681 {
1682     if (m_layoutDirty) {
1683         QStringList layout = extractLayout(nullptr, this);
1684         menuFile->setLayout(folderInfo()->fullId, layout);
1685         m_layoutDirty = false;
1686     }
1687 
1688     for (int i = 0; i < childCount(); ++i) {
1689         TreeItem *item = dynamic_cast<TreeItem *>(child(i));
1690         if (item) {
1691             item->saveLayout(menuFile);
1692         }
1693     }
1694 }
1695 
1696 void TreeView::saveLayout()
1697 {
1698     if (m_layoutDirty) {
1699         QStringList layout = extractLayout(this, nullptr);
1700         m_menuFile->setLayout(m_rootFolder->fullId, layout);
1701         m_layoutDirty = false;
1702     }
1703 
1704     for (int i = 0; i < topLevelItemCount(); ++i) {
1705         TreeItem *item = dynamic_cast<TreeItem *>(topLevelItem(i));
1706         if (item) {
1707             item->saveLayout(m_menuFile);
1708         }
1709     }
1710 }
1711 
1712 bool TreeView::save()
1713 {
1714     saveLayout();
1715     m_rootFolder->save(m_menuFile);
1716 
1717     bool success = m_menuFile->performAllActions();
1718 
1719     m_newMenuIds.clear();
1720     m_newDirectoryList.clear();
1721 
1722     if (success) {
1723         KBuildSycocaProgressDialog::rebuildKSycoca(this);
1724     } else {
1725         KMessageBox::error(this,
1726                            QStringLiteral("<qt>") + i18n("Menu changes could not be saved because of the following problem:") + QStringLiteral("<br><br>")
1727                                + m_menuFile->error() + QStringLiteral("</qt>"));
1728     }
1729 
1730     sendReloadMenu();
1731 
1732     return success;
1733 }
1734 
1735 void TreeView::setLayoutDirty(TreeItem *parentItem)
1736 {
1737     if (parentItem) {
1738         parentItem->setLayoutDirty();
1739     } else {
1740         m_layoutDirty = true;
1741     }
1742 }
1743 
1744 bool TreeView::isLayoutDirty()
1745 {
1746     for (int i = 0; i < topLevelItemCount(); ++i) {
1747         TreeItem *item = dynamic_cast<TreeItem *>(topLevelItem(i));
1748         if (!item) {
1749             continue;
1750         }
1751 
1752         if (item->isLayoutDirty()) {
1753             return true;
1754         }
1755     }
1756 
1757     return false;
1758 }
1759 
1760 bool TreeView::dirty()
1761 {
1762     return m_layoutDirty || m_rootFolder->hasDirt() || m_menuFile->dirty() || isLayoutDirty();
1763 }
1764 
1765 void TreeView::findServiceShortcut(const QKeySequence &cut, KService::Ptr &service)
1766 {
1767     service = m_rootFolder->findServiceShortcut(cut);
1768 }
1769 
1770 void TreeView::restoreMenuSystem()
1771 {
1772     if (KMessageBox::questionTwoActions(this,
1773                                         i18n("Do you want to restore the system menu? Warning: This will remove all custom menus."),
1774                                         i18nc("@title:window", "Restore Menu System"),
1775                                         KStandardGuiItem::reset(),
1776                                         KStandardGuiItem::cancel())
1777         == KMessageBox::SecondaryAction) {
1778         return;
1779     }
1780     const QString kmenueditfile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("/menus/applications-kmenuedit.menu");
1781     if (QFile::exists(kmenueditfile)) {
1782         if (!QFile::remove(kmenueditfile)) {
1783             qCWarning(KMENUEDIT_LOG) << "Could not delete " << kmenueditfile;
1784         }
1785     }
1786 
1787     const QString xdgappsdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/applications");
1788     if (QFileInfo(xdgappsdir).isDir()) {
1789         if (!QDir(xdgappsdir).removeRecursively()) {
1790             qCWarning(KMENUEDIT_LOG) << "Could not delete dir :" << xdgappsdir;
1791         }
1792     }
1793     const QString xdgdesktopdir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/desktop-directories");
1794     if (QFileInfo(xdgdesktopdir).isDir()) {
1795         if (!QDir(xdgdesktopdir).removeRecursively()) {
1796             qCWarning(KMENUEDIT_LOG) << "Could not delete dir :" << xdgdesktopdir;
1797         }
1798     }
1799 
1800     KBuildSycocaProgressDialog::rebuildKSycoca(this);
1801     clear();
1802     cleanupClipboard();
1803     delete m_rootFolder;
1804     delete m_separator;
1805 
1806     m_layoutDirty = false;
1807     m_newMenuIds.clear();
1808     m_newDirectoryList.clear();
1809     m_menuFile->restoreMenuSystem(kmenueditfile);
1810 
1811     m_rootFolder = new MenuFolderInfo;
1812     m_separator = new MenuSeparatorInfo;
1813 
1814     readMenuFolderInfo();
1815     fill();
1816     sendReloadMenu();
1817     emit disableAction();
1818     emit entrySelected((MenuEntryInfo *)nullptr);
1819 }
1820 
1821 void TreeView::updateTreeView(bool showHidden)
1822 {
1823     m_showHidden = showHidden;
1824     clear();
1825     cleanupClipboard();
1826     delete m_rootFolder;
1827     delete m_separator;
1828 
1829     m_layoutDirty = false;
1830     m_newMenuIds.clear();
1831     m_newDirectoryList.clear();
1832 
1833     m_rootFolder = new MenuFolderInfo;
1834     m_separator = new MenuSeparatorInfo;
1835 
1836     readMenuFolderInfo();
1837     fill();
1838     sendReloadMenu();
1839     emit disableAction();
1840     emit entrySelected((MenuEntryInfo *)nullptr);
1841 }
1842 
1843 void TreeView::sendReloadMenu()
1844 {
1845     QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/kickoff"), QStringLiteral("org.kde.plasma"), QStringLiteral("reloadMenu"));
1846     QDBusConnection::sessionBus().send(message);
1847 }
1848 
1849 // Slot to expand or retract items in tree based on length of search string
1850 void TreeView::searchUpdated(const QString &searchString)
1851 {
1852     // expand all categories if we typed more than a few characters, otherwise collapse and un-select everything
1853     // use logicalLength for CJK users
1854     if (KStringHandler::logicalLength(searchString) > 2) {
1855         expandAll();
1856     } else {
1857         collapseAll();
1858         setCurrentIndex(rootIndex());
1859     }
1860 }
1861 
1862 MenuItemMimeData::MenuItemMimeData(TreeItem *item)
1863     : QMimeData()
1864     , m_item(item)
1865 {
1866 }
1867 
1868 TreeItem *MenuItemMimeData::item() const
1869 {
1870     return m_item;
1871 }
1872 
1873 QStringList MenuItemMimeData::formats() const
1874 {
1875     QStringList formats;
1876     if (!m_item) {
1877         return formats;
1878     }
1879 
1880     formats << QLatin1String(s_internalMimeType);
1881     return formats;
1882 }
1883 
1884 bool MenuItemMimeData::hasFormat(const QString &mimeType) const
1885 {
1886     return m_item && mimeType == QLatin1String(s_internalMimeType);
1887 }
1888 
1889 QVariant MenuItemMimeData::retrieveData(const QString &mimeType, QMetaType type) const
1890 {
1891     Q_UNUSED(type);
1892 
1893     if (m_item && mimeType == QLatin1String(s_internalMimeType)) {
1894         return QVariant::fromValue<TreeItem *>(m_item);
1895     }
1896 
1897     return QVariant();
1898 }