File indexing completed on 2023-12-03 09:01:44
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 }