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 }