1/*
2 * Copyright (C) 2000 Matthias Elter <elter@kde.org>
3 * Copyright (C) 2001-2002 Raffaele Sandrini <sandrini@kde.org>
4 * Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
5 * Copyright (C) 2008 Laurent Montel <montel@kde.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23#include "treeview.h"
24
25#include <unistd.h>
26
27#include <QDir>
28#include <QHeaderView>
29#include <QPainter>
30#include <QRegExp>
31#include <QPixmap>
32#include <QDropEvent>
33#include <QMenu>
34#include <QApplication>
35#include <QtDBus/QtDBus>
36#include <QSignalMapper>
37
38#include <KAction>
39#include <KActionCollection>
40#include <KBuildSycocaProgressDialog>
41#include <KDebug>
42#include <KDesktopFile>
43#include <KGlobal>
44#include <KIconLoader>
45#include <KInputDialog>
46#include <KLocale>
47#include <KMessageBox>
48#include <KService>
49#include <KServiceGroup>
50#include <KConfig>
51#include <KConfigGroup>
52#include <KStandardDirs>
53#include <KIO/NetAccess>
54
55#include "treeview.moc"
56#include "menufile.h"
57#include "menuinfo.h"
58
59#define MOVE_FOLDER 'M'
60#define COPY_FOLDER 'C'
61#define MOVE_FILE 'm'
62#define COPY_FILE 'c'
63#define COPY_SEPARATOR 'S'
64
65static const char *s_internalMimeType = "application/x-kmenuedit-internal";
66
67class SeparatorWidget : public QWidget
68{
69public:
70 SeparatorWidget()
71 : QWidget(0)
72 {
73 }
74
75protected:
76 void paintEvent(QPaintEvent * /*event*/)
77 {
78 QPainter p(this);
79 // Draw Separator
80 int h = (height() / 2) -1;
81// if (isSelected()) {
82// p->setPen( palette().color( QPalette::HighlightedText ) );
83// } else {
84// p->setPen( palette().color( QPalette::Text ) );
85// }
86
87 p.drawLine(2, h, width() - 4, h);
88 }
89};
90
91
92TreeItem::TreeItem(QTreeWidgetItem *parent, QTreeWidgetItem *after, const QString& menuId, bool _m_init)
93 : QTreeWidgetItem(parent, after),
94 m_hidden(false),
95 m_init(_m_init),
96 m_layoutDirty(false),
97 m_menuId(menuId),
98 m_folderInfo(0),
99 m_entryInfo(0)
100{
101}
102
103TreeItem::TreeItem(QTreeWidget *parent, QTreeWidgetItem *after, const QString& menuId, bool _m_init)
104 : QTreeWidgetItem(parent, after),
105 m_hidden(false),
106 m_init(_m_init),
107 m_layoutDirty(false),
108 m_menuId(menuId),
109 m_folderInfo(0),
110 m_entryInfo(0)
111{
112 load();
113}
114
115TreeItem::~TreeItem()
116{
117}
118
119/**
120 * @brief Return the description.
121 * @return Description, or an empty string if none.
122 */
123QString TreeItem::description() const
124{
125 QString description;
126 if (isEntry()) {
127 description = entryInfo()->description;
128 }
129 return description;
130}
131
132/**
133 * @brief Compare two items using their names.
134 * @param item1 First item.
135 * @param item2 Second item.
136 * @return Integer less than, equal to, or greater than zero if item1 is less than, equal to, or greater than item2.
137 */
138bool TreeItem::itemNameLessThan(QTreeWidgetItem *item1, QTreeWidgetItem *item2)
139{
140 TreeItem *treeItem1 = static_cast<TreeItem*>(item1);
141 TreeItem *treeItem2 = static_cast<TreeItem*>(item2);
142 return treeItem1->name().toLower() < treeItem2->name().toLower();
143}
144
145/**
146 * @brief Compare two items using their descriptions. If both are empty, sort them by name.
147 * @param item1 First item.
148 * @param item2 Second item.
149 * @return Integer less than, equal to, or greater than zero if item1 is less than, equal to, or greater than item2.
150 */
151bool TreeItem::itemDescriptionLessThan(QTreeWidgetItem *item1, QTreeWidgetItem *item2)
152{
153 // extract descriptions in lower case
154 TreeItem *treeItem1 = static_cast<TreeItem*>(item1);
155 TreeItem *treeItem2 = static_cast<TreeItem*>(item2);
156 const QString description1 = treeItem1->description().toLower();
157 const QString description2 = treeItem2->description().toLower();
158
159 // if description is missing for both items, sort them using their names
160 if (description1.isEmpty() && description2.isEmpty()) {
161 return itemNameLessThan(item1, item2);
162 }
163 else {
164 return description1 < description2;
165 }
166}
167
168void TreeItem::setName(const QString &name)
169{
170 if (m_name == name) {
171 return;
172 }
173
174 m_name = name;
175 update();
176}
177
178void TreeItem::setHiddenInMenu(bool b)
179{
180 if (m_hidden == b) {
181 return;
182 }
183
184 m_hidden = b;
185 update();
186}
187
188void TreeItem::update()
189{
190 QString s = m_name;
191 if (m_hidden) {
192 s += i18n(" [Hidden]");
193 }
194
195 setText(0, s);
196}
197
198void TreeItem::load()
199{
200 if (m_folderInfo && !m_init) {
201 m_init = true;
202 TreeView *tv = static_cast<TreeView *>(treeWidget());
203 tv->fillBranch(m_folderInfo, this);
204 }
205}
206
207bool TreeItem::isLayoutDirty() const
208{
209 if (m_layoutDirty) {
210 return true;
211 }
212
213 for (int i = 0; i < childCount(); ++i) {
214 TreeItem *item = dynamic_cast<TreeItem *>(child(i));
215 if (!item) {
216 continue;
217 }
218
219 if (item->isLayoutDirty()) {
220 return true;
221 }
222 }
223
224 return false;
225}
226
227static QPixmap appIcon(const QString &iconName)
228{
229 QPixmap normal = KIconLoader::global()->loadIcon(iconName, KIconLoader::Small, 0, KIconLoader::DefaultState, QStringList(), 0L, true);
230 // make sure they are not larger than 20x20
231 if (normal.width() > 20 || normal.height() > 20)
232 {
233 QImage tmp = normal.toImage();
234 tmp = tmp.scaled(20, 20, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
235 normal = QPixmap::fromImage(tmp);
236 }
237 return normal;
238}
239
240
241TreeView::TreeView( KActionCollection *ac, QWidget *parent, const char *name )
242 : QTreeWidget(parent), m_ac(ac), m_popupMenu(0), m_clipboard(0),
243 m_clipboardFolderInfo(0), m_clipboardEntryInfo(0),
244 m_layoutDirty(false),
245 m_detailedMenuEntries(true), m_detailedEntriesNamesFirst(true)
246{
247 m_dropMimeTypes << s_internalMimeType << KUrl::List::mimeDataTypes();
248 qRegisterMetaType<TreeItem *>("TreeItem");
249 setObjectName(name);
250 setAllColumnsShowFocus(true);
251 setRootIsDecorated(true);
252 setSortingEnabled(false);
253 setDragEnabled(true);
254 setAcceptDrops(true);
255 setMinimumWidth(240);
256
257 setHeaderLabels(QStringList() << QString(""));
258 header()->hide();
259
260 // listen for creation
261 connect(m_ac->action(NEW_ITEM_ACTION_NAME), SIGNAL(activated()), SLOT(newitem()));
262 connect(m_ac->action(NEW_SUBMENU_ACTION_NAME), SIGNAL(activated()), SLOT(newsubmenu()));
263 connect(m_ac->action(NEW_SEPARATOR_ACTION_NAME), SIGNAL(activated()), SLOT(newsep()));
264
265 // listen for copy
266 connect(m_ac->action(CUT_ACTION_NAME), SIGNAL(activated()), SLOT(cut()));
267 connect(m_ac->action(COPY_ACTION_NAME), SIGNAL(activated()), SLOT(copy()));
268 connect(m_ac->action(PASTE_ACTION_NAME), SIGNAL(activated()), SLOT(paste()));
269
270 // listen for deleting
271 connect(m_ac->action(DELETE_ACTION_NAME), SIGNAL(activated()), SLOT(del()));
272
273 // listen for sorting
274 m_sortSignalMapper = new QSignalMapper(this);
275 QAction *action = m_ac->action(SORT_BY_NAME_ACTION_NAME);
276 connect(action, SIGNAL(activated()), m_sortSignalMapper, SLOT(map()));
277 m_sortSignalMapper->setMapping(action, SortByName);
278 action = m_ac->action(SORT_BY_DESCRIPTION_ACTION_NAME);
279 connect(action, SIGNAL(activated()), m_sortSignalMapper, SLOT(map()));
280 m_sortSignalMapper->setMapping(action, SortByDescription);
281 action = m_ac->action(SORT_ALL_BY_NAME_ACTION_NAME);
282 connect(action, SIGNAL(activated()), m_sortSignalMapper, SLOT(map()));
283 m_sortSignalMapper->setMapping(action, SortAllByName);
284 action = m_ac->action(SORT_ALL_BY_DESCRIPTION_ACTION_NAME);
285 connect(action, SIGNAL(activated()), m_sortSignalMapper, SLOT(map()));
286 m_sortSignalMapper->setMapping(action, SortAllByDescription);
287 connect(m_sortSignalMapper, SIGNAL(mapped(const int)), this, SLOT(sort(const int)));
288
289 // connect moving up/down actions
290 connect(m_ac->action(MOVE_UP_ACTION_NAME), SIGNAL(activated()), SLOT(moveUpItem()));
291 connect(m_ac->action(MOVE_DOWN_ACTION_NAME), SIGNAL(activated()), SLOT(moveDownItem()));
292
293 // listen for selection
294 connect(this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
295 SLOT(itemSelected(QTreeWidgetItem*)));
296
297 m_menuFile = new MenuFile(KStandardDirs::locateLocal("xdgconf-menu", "applications-kmenuedit.menu"));
298 m_rootFolder = new MenuFolderInfo;
299 m_separator = new MenuSeparatorInfo;
300}
301
302TreeView::~TreeView()
303{
304 cleanupClipboard();
305 delete m_rootFolder;
306 delete m_separator;
307}
308
309void TreeView::setViewMode(bool showHidden)
310{
311 // setup popup menu
312 delete m_popupMenu;
313 m_popupMenu = new QMenu(this);
314
315 // creation
316 m_popupMenu->addAction(m_ac->action(NEW_ITEM_ACTION_NAME));
317 m_popupMenu->addAction(m_ac->action(NEW_SUBMENU_ACTION_NAME));
318 m_popupMenu->addAction(m_ac->action(NEW_SEPARATOR_ACTION_NAME));
319 m_popupMenu->addSeparator();
320
321 // copy
322 m_popupMenu->addAction(m_ac->action(CUT_ACTION_NAME));
323 m_popupMenu->addAction(m_ac->action(COPY_ACTION_NAME));
324 m_popupMenu->addAction(m_ac->action(PASTE_ACTION_NAME));
325 m_popupMenu->addSeparator();
326
327 // delete
328 m_popupMenu->addAction( m_ac->action(DELETE_ACTION_NAME));
329 m_popupMenu->addSeparator();
330
331 // move
332 m_popupMenu->addAction(m_ac->action(MOVE_UP_ACTION_NAME));
333 m_popupMenu->addAction(m_ac->action(MOVE_DOWN_ACTION_NAME));
334 m_popupMenu->addSeparator();
335
336 // sort
337 m_popupMenu->addAction(m_ac->action(SORT_ACTION_NAME));
338
339 m_showHidden = showHidden;
340 readMenuFolderInfo();
341 fill();
342}
343
344void TreeView::readMenuFolderInfo(MenuFolderInfo *folderInfo, KServiceGroup::Ptr folder, const QString &prefix)
345{
346 if (!folderInfo)
347 {
348 folderInfo = m_rootFolder;
349 folder = KServiceGroup::root();
350 }
351
352 if (!folder || !folder->isValid())
353 return;
354
355 folderInfo->caption = folder->caption();
356 folderInfo->comment = folder->comment();
357
358 // Item names may contain ampersands. To avoid them being converted
359 // to accelerators, replace them with two ampersands.
360 folderInfo->hidden = folder->noDisplay();
361 folderInfo->directoryFile = folder->directoryEntryPath();
362 folderInfo->icon = folder->icon();
363 QString id = folder->relPath();
364 int i = id.lastIndexOf('/', -2);
365 id = id.mid(i+1);
366 folderInfo->id = id;
367 folderInfo->fullId = prefix + id;
368
369 foreach(const KSycocaEntry::Ptr &e, folder->entries(true, !m_showHidden, true, m_detailedMenuEntries && !m_detailedEntriesNamesFirst))
370 {
371 if (e->isType(KST_KServiceGroup))
372 {
373 KServiceGroup::Ptr g(KServiceGroup::Ptr::staticCast(e));
374 MenuFolderInfo *subFolderInfo = new MenuFolderInfo();
375 readMenuFolderInfo(subFolderInfo, g, folderInfo->fullId);
376 folderInfo->add(subFolderInfo, true);
377 }
378 else if (e->isType(KST_KService))
379 {
380 folderInfo->add(new MenuEntryInfo(KService::Ptr::staticCast(e)), true);
381 }
382 else if (e->isType(KST_KServiceSeparator))
383 {
384 folderInfo->add(m_separator, true);
385 }
386 }
387}
388
389void TreeView::fill()
390{
391 QApplication::setOverrideCursor(Qt::WaitCursor);
392 clear();
393 fillBranch(m_rootFolder, 0);
394 QApplication::restoreOverrideCursor();
395}
396
397QString TreeView::findName(KDesktopFile *df, bool deleted)
398{
399 QString name = df->readName();
400 if (deleted)
401 {
402 if (name == "empty")
403 name.clear();
404 if (name.isEmpty())
405 {
406 QString file = df->fileName();
407 QString res = df->resource();
408
409 bool isLocal = true;
410 const QStringList files = KGlobal::dirs()->findAllResources(res.toLatin1(), file);
411 for(QStringList::ConstIterator it = files.constBegin();
412 it != files.constEnd();
413 ++it)
414 {
415 if (isLocal)
416 {
417 isLocal = false;
418 continue;
419 }
420
421 KDesktopFile df2(*it);
422 name = df2.readName();
423
424 if (!name.isEmpty() && (name != "empty"))
425 return name;
426 }
427 }
428 }
429 return name;
430}
431
432TreeItem *TreeView::createTreeItem(TreeItem *parent, QTreeWidgetItem *after, MenuFolderInfo *folderInfo, bool m_init)
433{
434 TreeItem *item;
435 if (parent) {
436 item = new TreeItem(parent, after, QString(), m_init);
437 } else {
438 item = new TreeItem(this, after, QString(), m_init);
439 }
440
441 item->setMenuFolderInfo(folderInfo);
442 item->setName(folderInfo->caption);
443 item->setIcon(0, appIcon(folderInfo->icon));
444 item->setDirectoryPath(folderInfo->fullId);
445 item->setHiddenInMenu(folderInfo->hidden);
446 item->load();
447 return item;
448}
449
450TreeItem *TreeView::createTreeItem(TreeItem *parent, QTreeWidgetItem *after, MenuEntryInfo *entryInfo, bool m_init)
451{
452 bool hidden = entryInfo->hidden;
453
454 TreeItem* item;
455 if (parent) {
456 item = new TreeItem(parent, after, entryInfo->menuId(),m_init);
457 } else {
458 item = new TreeItem(this, after, entryInfo->menuId(), m_init);
459 }
460
461 QString name;
462
463 if (m_detailedMenuEntries && entryInfo->description.length() != 0) {
464 if (m_detailedEntriesNamesFirst) {
465 name = entryInfo->caption + " (" + entryInfo->description + ')';
466 } else {
467 name = entryInfo->description + " (" + entryInfo->caption + ')';
468 }
469 } else {
470 name = entryInfo->caption;
471 }
472
473 //kDebug() << parent << after << name;
474 item->setMenuEntryInfo(entryInfo);
475 item->setName(name);
476 item->setIcon(0, appIcon(entryInfo->icon));
477 item->setHiddenInMenu(hidden);
478 item->load();
479
480 return item;
481}
482
483TreeItem *TreeView::createTreeItem(TreeItem *parent, QTreeWidgetItem *after, MenuSeparatorInfo *, bool init)
484{
485 TreeItem* item;
486 if (parent) {
487 item = new TreeItem(parent, after, QString(), init);
488 } else {
489 item = new TreeItem(this, after, QString(), init);
490 }
491
492 setItemWidget(item, 0, new SeparatorWidget);
493 return item;
494}
495
496void TreeView::fillBranch(MenuFolderInfo *folderInfo, TreeItem *parent)
497{
498 QString relPath = parent ? parent->directory() : QString();
499 TreeItem *after = 0;
500 foreach (MenuInfo *info, folderInfo->initialLayout)
501 {
502 MenuEntryInfo *entry = dynamic_cast<MenuEntryInfo*>(info);
503 if (entry)
504 {
505 after = createTreeItem(parent, after, entry);
506 continue;
507 }
508
509 MenuFolderInfo *subFolder = dynamic_cast<MenuFolderInfo*>(info);
510 if (subFolder)
511 {
512 after = createTreeItem(parent, after, subFolder);
513 continue;
514 }
515 MenuSeparatorInfo *separator = dynamic_cast<MenuSeparatorInfo*>(info);
516 if (separator)
517 {
518 after = createTreeItem(parent, after, separator);
519 continue;
520 }
521 }
522}
523
524void TreeView::closeAllItems(QTreeWidgetItem *item)
525{
526 item->setExpanded(false);
527 for (int i = 0; i < item->childCount(); ++i) {
528 closeAllItems(item->child(i));
529 }
530}
531
532TreeItem *TreeView::expandPath(TreeItem *item, const QString &path)
533{
534 int i = path.indexOf("/");
535 QString subMenu = path.left(i+1);
536 QString restMenu = path.mid(i+1);
537
538 for (int i = 0; i < item->childCount(); ++i) {
539 TreeItem *childItem = dynamic_cast<TreeItem *>(item->child(i));
540 if (!childItem) {
541 continue;
542 }
543
544 MenuFolderInfo *folderInfo = childItem->folderInfo();
545 if (folderInfo && (folderInfo->id == subMenu)) {
546 childItem->setExpanded(true);
547 if (!restMenu.isEmpty()) {
548 return expandPath(childItem, restMenu);
549 } else {
550 return childItem;
551 }
552 }
553 }
554
555 return 0;
556}
557
558void TreeView::selectMenu(const QString &menu)
559{
560 for (int i = 0; i < topLevelItemCount(); ++i) {
561 closeAllItems(topLevelItem(i));
562 }
563
564 if (menu.length() <= 1)
565 {
566 setCurrentItem(topLevelItem(0));
567 clearSelection();
568 return; // Root menu
569 }
570
571 QString restMenu = menu;
572 if ( menu.startsWith( '/' ) )
573 restMenu = menu.mid(1);
574 if (!restMenu.endsWith('/'))
575 restMenu += '/';
576
577 TreeItem *item = 0;
578 int i = restMenu.indexOf("/");
579 QString subMenu = restMenu.left(i+1);
580 restMenu = restMenu.mid(i+1);
581
582 for (int i = 0; i < topLevelItemCount(); ++i) {
583 item = dynamic_cast<TreeItem *>(topLevelItem(i));
584 if (!item) {
585 continue;
586 }
587
588 MenuFolderInfo *folderInfo = item->folderInfo();
589 if (folderInfo && (folderInfo->id == subMenu)) {
590 if (!restMenu.isEmpty()) {
591 item = expandPath(item, restMenu);
592 }
593 break;
594 }
595 }
596
597 if (item)
598 {
599 setCurrentItem(item);
600 scrollToItem(item);
601 }
602}
603
604void TreeView::selectMenuEntry(const QString &menuEntry)
605{
606 TreeItem *item = static_cast<TreeItem *>(selectedItem());
607 if (!item) {
608 item = static_cast<TreeItem *>(currentItem());
609 }
610
611 if (!item) {
612 return;
613 }
614
615 QTreeWidgetItem *parent = item->parent();
616 if (parent) {
617 for (int i = 0; i < parent->childCount(); ++i) {
618 TreeItem *item = dynamic_cast<TreeItem *>(parent->child(i));
619 if (!item || item->isDirectory()) {
620 continue;
621 }
622
623 MenuEntryInfo *entry = item->entryInfo();
624 if (entry && entry->menuId() == menuEntry) {
625 setCurrentItem(item);
626 scrollToItem(item);
627 return;
628 }
629 }
630 } else {
631 // top level
632 for (int i = 0; i < topLevelItemCount(); ++i) {
633 TreeItem *item = dynamic_cast<TreeItem *>(topLevelItem(i));
634 if (!item || item->isDirectory()) {
635 continue;
636 }
637
638 MenuEntryInfo *entry = item->entryInfo();
639 if (entry && entry->menuId() == menuEntry) {
640 setCurrentItem(item);
641 scrollToItem(item);
642 return;
643 }
644 }
645 }
646}
647
648void TreeView::itemSelected(QTreeWidgetItem *item)
649{
650 // ensure the item is visible as selected
651 setItemSelected(item, true);
652
653 TreeItem *_item = static_cast<TreeItem*>(item);
654 TreeItem *parentItem = 0;
655 bool selected = false;
656 bool dselected = false;
657 if (_item) {
658 selected = true;
659 dselected = _item->isHiddenInMenu();
660 parentItem = getParentItem(_item);
661 }
662
663 // change actions activation
664 m_ac->action(CUT_ACTION_NAME)->setEnabled(selected);
665 m_ac->action(COPY_ACTION_NAME)->setEnabled(selected);
666 m_ac->action(PASTE_ACTION_NAME)->setEnabled(m_clipboard != 0);
667
668 if (m_ac->action(DELETE_ACTION_NAME)) {
669 m_ac->action(DELETE_ACTION_NAME)->setEnabled(selected && !dselected);
670 }
671
672 m_ac->action(SORT_BY_NAME_ACTION_NAME)->setEnabled(selected && _item->isDirectory() && (_item->childCount() > 0));
673 m_ac->action(SORT_BY_DESCRIPTION_ACTION_NAME)->setEnabled(m_ac->action(SORT_BY_NAME_ACTION_NAME)->isEnabled());
674
675 m_ac->action(MOVE_UP_ACTION_NAME)->setEnabled(selected && (parentItem->indexOfChild(_item) > 0));
676 m_ac->action(MOVE_DOWN_ACTION_NAME)->setEnabled(selected && (parentItem->indexOfChild(_item) < parentItem->childCount() - 1));
677
678 if (!item) {
679 emit disableAction();
680 return;
681 }
682
683 if (_item->isDirectory()) {
684 emit entrySelected(_item->folderInfo());
685 } else {
686 emit entrySelected(_item->entryInfo());
687 }
688}
689
690void TreeView::currentDataChanged(MenuFolderInfo *folderInfo)
691{
692 TreeItem *item = (TreeItem*)selectedItem();
693 if (item == 0 || folderInfo == 0) {
694 return;
695 }
696
697 item->setName(folderInfo->caption);
698 item->setIcon(0, appIcon(folderInfo->icon));
699}
700
701void TreeView::currentDataChanged(MenuEntryInfo *entryInfo)
702{
703 TreeItem *item = (TreeItem*)selectedItem();
704 if (item == 0 || entryInfo == 0) {
705 return;
706 }
707
708 QString name;
709
710 if (m_detailedMenuEntries && entryInfo->description.length() != 0) {
711 if (m_detailedEntriesNamesFirst) {
712 name = entryInfo->caption + " (" + entryInfo->description + ')';
713 } else {
714 name = entryInfo->description + " (" + entryInfo->caption + ')';
715 }
716 } else {
717 name = entryInfo->caption;
718 }
719
720 item->setName(name);
721 item->setIcon(0, appIcon(entryInfo->icon));
722}
723
724QStringList TreeView::fileList(const QString& rPath)
725{
726 QString relativePath = rPath;
727
728 // truncate "/.directory"
729 int pos = relativePath.lastIndexOf("/.directory");
730 if (pos > 0) relativePath.truncate(pos);
731
732 QStringList filelist;
733
734 // loop through all resource dirs and build a file list
735 const QStringList resdirlist = KGlobal::dirs()->resourceDirs("apps");
736 for (QStringList::ConstIterator it = resdirlist.constBegin(); it != resdirlist.constEnd(); ++it)
737 {
738 QDir dir((*it) + '/' + relativePath);
739 if(!dir.exists()) continue;
740
741 dir.setFilter(QDir::Files);
742 dir.setNameFilters(QStringList() << "*.desktop;*.kdelnk");
743
744 // build a list of files
745 const QStringList files = dir.entryList();
746 for (QStringList::ConstIterator it = files.constBegin(); it != files.constEnd(); ++it) {
747 // does not work?!
748 //if (filelist.contains(*it)) continue;
749
750 if (relativePath.isEmpty()) {
751 filelist.removeAll(*it); // hack
752 filelist.append(*it);
753 }
754 else {
755 filelist.removeAll(relativePath + '/' + *it); //hack
756 filelist.append(relativePath + '/' + *it);
757 }
758 }
759 }
760 return filelist;
761}
762
763QStringList TreeView::dirList(const QString& rPath)
764{
765 QString relativePath = rPath;
766
767 // truncate "/.directory"
768 int pos = relativePath.lastIndexOf("/.directory");
769 if (pos > 0) relativePath.truncate(pos);
770
771 QStringList dirlist;
772
773 // loop through all resource dirs and build a subdir list
774 const QStringList resdirlist = KGlobal::dirs()->resourceDirs("apps");
775 for (QStringList::ConstIterator it = resdirlist.constBegin(); it != resdirlist.constEnd(); ++it)
776 {
777 QDir dir((*it) + '/' + relativePath);
778 if(!dir.exists()) continue;
779 dir.setFilter(QDir::Dirs);
780
781 // build a list of subdirs
782 const QStringList subdirs = dir.entryList();
783 for (QStringList::ConstIterator it = subdirs.constBegin(); it != subdirs.constEnd(); ++it) {
784 if ((*it) == "." || (*it) == "..") continue;
785 // does not work?!
786 // if (dirlist.contains(*it)) continue;
787
788 if (relativePath.isEmpty()) {
789 dirlist.removeAll(*it); //hack
790 dirlist.append(*it);
791 }
792 else {
793 dirlist.removeAll(relativePath + '/' + *it); //hack
794 dirlist.append(relativePath + '/' + *it);
795 }
796 }
797 }
798 return dirlist;
799}
800
801Qt::DropActions TreeView::supportedDropActions() const
802{
803 return Qt::CopyAction | Qt::MoveAction;
804}
805
806QStringList TreeView::mimeTypes() const
807{
808 return m_dropMimeTypes;
809}
810
811void TreeView::startDrag(Qt::DropActions supportedActions)
812{
813 QList<QTreeWidgetItem *> items;
814 items << selectedItem();
815 QMimeData *data = mimeData(items);
816 if (!data) {
817 return;
818 }
819
820 QDrag *drag = new QDrag(this);
821 drag->setPixmap(selectedItem()->icon(0).pixmap(24, 24));
822 drag->setMimeData(data);
823 drag->exec(supportedActions, Qt::MoveAction);
824}
825
826QMimeData *TreeView::mimeData(const QList<QTreeWidgetItem *> items) const
827{
828 if (items.isEmpty()) {
829 return 0;
830 }
831
832 return new MenuItemMimeData(dynamic_cast<TreeItem *>(items.first()));
833}
834
835static QString createDesktopFile(const QString &file, QString *menuId, QStringList *excludeList)
836{
837 QString base = file.mid(file.lastIndexOf('/')+1);
838 base = base.left(base.lastIndexOf('.'));
839
840 QRegExp r("(.*)(?=-\\d+)");
841 base = (r.indexIn(base) > -1) ? r.cap(1) : base;
842
843 QString result = KService::newServicePath(true, base, menuId, excludeList);
844 excludeList->append(*menuId);
845 // Todo for Undo-support: Undo menuId allocation:
846
847 return result;
848}
849
850static KDesktopFile *copyDesktopFile(MenuEntryInfo *entryInfo, QString *menuId, QStringList *excludeList)
851{
852 QString result = createDesktopFile(entryInfo->file(), menuId, excludeList);
853 KDesktopFile *df = entryInfo->desktopFile()->copyTo(result);
854 df->desktopGroup().deleteEntry("Categories"); // Don't set any categories!
855
856 return df;
857}
858
859static QString createDirectoryFile(const QString &file, QStringList *excludeList)
860{
861 QString base = file.mid(file.lastIndexOf('/')+1);
862 base = base.left(base.lastIndexOf('.'));
863
864 QString result;
865 int i = 1;
866 while(true)
867 {
868 if (i == 1)
869 result = base + ".directory";
870 else
871 result = base + QString("-%1.directory").arg(i);
872
873 if (!excludeList->contains(result))
874 {
875 if (KStandardDirs::locate("xdgdata-dirs", result).isEmpty())
876 break;
877 }
878 i++;
879 }
880 excludeList->append(result);
881 result = KStandardDirs::locateLocal("xdgdata-dirs", result);
882 return result;
883}
884
885
886bool TreeView::dropMimeData(QTreeWidgetItem *item, int index, const QMimeData *data, Qt::DropAction action)
887{
888 // get destination folder
889 TreeItem *titem = item ? dynamic_cast<TreeItem*>(item) : 0;
890 if (item && !titem) {
891 return false;
892 }
893
894 TreeItem *parentItem = 0;
895 QTreeWidgetItem *after = titem;
896 // find the parent item and which item the dropped item should go after
897 if (titem) {
898 if (titem->isDirectory()) {
899 parentItem = titem;
900 after = titem->child(index);
901 if (!after) {
902 after = titem->child(titem->childCount() - 1);
903 }
904 } else {
905 parentItem = dynamic_cast<TreeItem *>(titem->parent());
906 if (titem->parent() && !parentItem) {
907 return false;
908 }
909 }
910 } else if (index > 0) {
911 after = topLevelItem(index);
912 if (!after) {
913 after = topLevelItem(topLevelItemCount() - 1);
914 }
915 }
916
917 QString folder = parentItem ? parentItem->directory() : "/";
918 MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
919 kDebug() << "think we're dropping on" << (parentItem ? parentItem->text(0) : "Top Level") << index;
920
921 if (!data->hasFormat(s_internalMimeType)) {
922 // External drop
923 if (!KUrl::List::canDecode(data)) {
924 return false;
925 }
926
927 KUrl::List urls = KUrl::List::fromMimeData(data);;
928 if (urls.isEmpty() || !urls[0].isLocalFile()) {
929 return false;
930 }
931
932 //FIXME: this should really support multiple DnD
933 QString path = urls[0].path();
934 if (!path.endsWith(QLatin1String(".desktop"))) {
935 return false;
936 }
937
938 QString menuId;
939 QString result = createDesktopFile(path, &menuId, &m_newMenuIds);
940 KDesktopFile orig_df(path);
941 KDesktopFile *df = orig_df.copyTo(result);
942 df->desktopGroup().deleteEntry("Categories"); // Don't set any categories!
943
944 KService::Ptr s(new KService(df));
945 s->setMenuId(menuId);
946
947 MenuEntryInfo *entryInfo = new MenuEntryInfo(s, df);
948
949 QString oldCaption = entryInfo->caption;
950 QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption);
951 entryInfo->setCaption(newCaption);
952
953 // Add file to menu
954 // m_menuFile->addEntry(folder, menuId);
955 m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
956
957 // create the TreeItem
958 if (parentItem) {
959 parentItem->setExpanded(true);
960 }
961
962 // update fileInfo data
963 parentFolderInfo->add(entryInfo);
964
965 TreeItem *newItem = createTreeItem(parentItem, after, entryInfo, true);
966 setCurrentItem(newItem);
967
968 setLayoutDirty(parentItem);
969 return true;
970 }
971
972 QVariant p(data->data(s_internalMimeType));
973 const MenuItemMimeData *itemData = dynamic_cast<const MenuItemMimeData *>(data);
974 if (!itemData) {
975 return false;
976 }
977
978 TreeItem *dragItem = itemData->item();
979 if (!dragItem || dragItem == after) {
980 return false; // Nothing to do
981 }
982
983 //kDebug() << "an internal drag of" << dragItem->text(0) << (parentItem ? parentItem->text(0) : "Top level");
984 if (dragItem->isDirectory()) {
985 MenuFolderInfo *folderInfo = dragItem->folderInfo();
986 if (action == Qt::CopyAction) {
987 // FIXME:
988 // * Create new .directory file
989 } else {
990 TreeItem *tmpItem = static_cast<TreeItem*>(parentItem);
991 while (tmpItem) {
992 if (tmpItem == dragItem) {
993 return false;
994 }
995
996 tmpItem = static_cast<TreeItem*>(tmpItem->parent());
997 }
998
999 // Remove MenuFolderInfo
1000 TreeItem *oldParentItem = static_cast<TreeItem*>(dragItem->parent());
1001 MenuFolderInfo *oldParentFolderInfo = oldParentItem ? oldParentItem->folderInfo() : m_rootFolder;
1002 oldParentFolderInfo->take(folderInfo);
1003
1004 // Move menu
1005 QString oldFolder = folderInfo->fullId;
1006 QString folderName = folderInfo->id;
1007 QString newFolder = m_menuFile->uniqueMenuName(folder, folderName, parentFolderInfo->existingMenuIds());
1008 folderInfo->id = newFolder;
1009
1010 // Add file to menu
1011 //m_menuFile->moveMenu(oldFolder, folder + newFolder);
1012 kDebug() << "moving" << dragItem->text(0) << "to" << folder + newFolder;
1013 m_menuFile->pushAction(MenuFile::MOVE_MENU, oldFolder, folder + newFolder);
1014
1015 // Make sure caption is unique
1016 QString newCaption = parentFolderInfo->uniqueMenuCaption(folderInfo->caption);
1017 if (newCaption != folderInfo->caption) {
1018 folderInfo->setCaption(newCaption);
1019 }
1020
1021 // create the TreeItem
1022 if (parentItem) {
1023 parentItem->setExpanded(true);
1024 }
1025
1026 // update fileInfo data
1027 folderInfo->updateFullId(parentFolderInfo->fullId);
1028 folderInfo->setInUse(true);
1029 parentFolderInfo->add(folderInfo);
1030
1031 if (parentItem != oldParentItem) {
1032 if (oldParentItem) {
1033 oldParentItem->takeChild(oldParentItem->indexOfChild(dragItem));
1034 } else {
1035 takeTopLevelItem(indexOfTopLevelItem(dragItem));
1036 }
1037 }
1038
1039 if (parentItem) {
1040 parentItem->insertChild(after ? parentItem->indexOfChild(after) + 1 : parentItem->childCount(), dragItem);
1041 } else {
1042 insertTopLevelItem(after ? indexOfTopLevelItem(after) : topLevelItemCount(), dragItem);
1043 }
1044
1045 dragItem->setName(folderInfo->caption);
1046 dragItem->setDirectoryPath(folderInfo->fullId);
1047 setCurrentItem(dragItem);
1048 }
1049 } else if (dragItem->isEntry()) {
1050 MenuEntryInfo *entryInfo = dragItem->entryInfo();
1051 QString menuId = entryInfo->menuId();
1052
1053 if (action == Qt::CopyAction) {
1054 // Need to copy file and then add it
1055 KDesktopFile *df = copyDesktopFile(entryInfo, &menuId, &m_newMenuIds); // Duplicate
1056 //UNDO-ACTION: NEW_MENU_ID (menuId)
1057
1058 KService::Ptr s(new KService(df));
1059 s->setMenuId(menuId);
1060
1061 entryInfo = new MenuEntryInfo(s, df);
1062
1063 QString oldCaption = entryInfo->caption;
1064 QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption);
1065 entryInfo->setCaption(newCaption);
1066 } else {
1067 del(dragItem, false);
1068 QString oldCaption = entryInfo->caption;
1069 QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption);
1070 entryInfo->setCaption(newCaption);
1071 entryInfo->setInUse(true);
1072 }
1073
1074 // Add file to menu
1075 // m_menuFile->addEntry(folder, menuId);
1076 m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
1077
1078 // create the TreeItem
1079 if (parentItem) {
1080 parentItem->setExpanded(true);
1081 }
1082
1083 // update fileInfo data
1084 parentFolderInfo->add(entryInfo);
1085
1086 TreeItem *newItem = createTreeItem(parentItem, after, entryInfo);
1087 setCurrentItem(newItem);
1088 } else {
1089 // copying a separator
1090 if (action != Qt::CopyAction) {
1091 del(dragItem, false);
1092 }
1093
1094 TreeItem *newItem = createTreeItem(parentItem, after, m_separator);
1095 setCurrentItem(newItem);
1096 }
1097
1098 kDebug() << "setting the layout to be dirty at" << parentItem;
1099 setLayoutDirty(parentItem);
1100 return true;
1101}
1102
1103
1104QTreeWidgetItem *TreeView::selectedItem()
1105{
1106 QList<QTreeWidgetItem *> selection = selectedItems();
1107
1108 if (selection.isEmpty()) {
1109 return 0;
1110 }
1111
1112 return selection.first();
1113}
1114
1115void TreeView::contextMenuEvent(QContextMenuEvent *event)
1116{
1117 if (m_popupMenu && itemAt(event->pos())) {
1118 m_popupMenu->exec(event->globalPos());
1119 }
1120}
1121
1122void TreeView::dropEvent(QDropEvent *event)
1123{
1124 // this prevents QTreeWidget from interfering with our moves
1125 QTreeView::dropEvent(event);
1126}
1127
1128void TreeView::newsubmenu()
1129{
1130 TreeItem *parentItem = 0;
1131 TreeItem *item = (TreeItem*)selectedItem();
1132
1133 bool ok;
1134 QString caption = KInputDialog::getText( i18n( "New Submenu" ),
1135 i18n( "Submenu name:" ), QString(), &ok, this );
1136
1137 if (!ok) return;
1138
1139 QString file = caption;
1140 file.replace('/', '-');
1141
1142 file = createDirectoryFile(file, &m_newDirectoryList); // Create
1143
1144 // get destination folder
1145 QString folder;
1146
1147 if(!item)
1148 {
1149 parentItem = 0;
1150 folder.clear();
1151 }
1152 else if(item->isDirectory())
1153 {
1154 parentItem = item;
1155 item = 0;
1156 folder = parentItem->directory();
1157 }
1158 else
1159 {
1160 parentItem = static_cast<TreeItem*>(item->parent());
1161 folder = parentItem ? parentItem->directory() : QString();
1162 }
1163
1164 MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1165 MenuFolderInfo *folderInfo = new MenuFolderInfo();
1166 folderInfo->caption = parentFolderInfo->uniqueMenuCaption(caption);
1167 folderInfo->id = m_menuFile->uniqueMenuName(folder, caption, parentFolderInfo->existingMenuIds());
1168 folderInfo->directoryFile = file;
1169 folderInfo->icon = "package";
1170 folderInfo->hidden = false;
1171 folderInfo->setDirty();
1172
1173 KDesktopFile *df = new KDesktopFile(file);
1174 KConfigGroup desktopGroup = df->desktopGroup();
1175 desktopGroup.writeEntry("Name", folderInfo->caption);
1176 desktopGroup.writeEntry("Icon", folderInfo->icon);
1177 df->sync();
1178 delete df;
1179 // Add file to menu
1180 // m_menuFile->addMenu(folder + folderInfo->id, file);
1181 m_menuFile->pushAction(MenuFile::ADD_MENU, folder + folderInfo->id, file);
1182
1183 folderInfo->fullId = parentFolderInfo->fullId + folderInfo->id;
1184
1185 // create the TreeItem
1186 if (parentItem)
1187 parentItem->setExpanded(true);
1188
1189 // update fileInfo data
1190 parentFolderInfo->add(folderInfo);
1191
1192 TreeItem *newItem = createTreeItem(parentItem, item, folderInfo, true);
1193
1194 setCurrentItem(newItem);
1195 setLayoutDirty(parentItem);
1196}
1197
1198void TreeView::newitem()
1199{
1200 TreeItem *parentItem = 0;
1201 TreeItem *item = (TreeItem*)selectedItem();
1202
1203 bool ok;
1204 QString caption = KInputDialog::getText( i18n( "New Item" ),
1205 i18n( "Item name:" ), QString(), &ok, this );
1206
1207 if (!ok) return;
1208
1209 QString menuId;
1210 QString file = caption;
1211 file.replace('/', '-');
1212
1213 file = createDesktopFile(file, &menuId, &m_newMenuIds); // Create
1214
1215 KDesktopFile *df = new KDesktopFile(file);
1216 KConfigGroup desktopGroup = df->desktopGroup();
1217 desktopGroup.writeEntry("Name", caption);
1218 desktopGroup.writeEntry("Type", "Application");
1219
1220 // get destination folder
1221 QString folder;
1222
1223 if(!item)
1224 {
1225 parentItem = 0;
1226 folder.clear();
1227 }
1228 else if(item->isDirectory())
1229 {
1230 parentItem = item;
1231 item = 0;
1232 folder = parentItem->directory();
1233 }
1234 else
1235 {
1236 parentItem = static_cast<TreeItem*>(item->parent());
1237 folder = parentItem ? parentItem->directory() : QString();
1238 }
1239
1240 MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1241
1242 // Add file to menu
1243 // m_menuFile->addEntry(folder, menuId);
1244 m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
1245
1246 KService::Ptr s(new KService(df));
1247 s->setMenuId(menuId);
1248
1249 MenuEntryInfo *entryInfo = new MenuEntryInfo(s, df);
1250
1251 // create the TreeItem
1252 if(parentItem)
1253 parentItem->setExpanded(true);
1254
1255 // update fileInfo data
1256 parentFolderInfo->add(entryInfo);
1257
1258 TreeItem *newItem = createTreeItem(parentItem, item, entryInfo, true);
1259
1260 setCurrentItem(newItem);
1261 setLayoutDirty(parentItem);
1262}
1263
1264void TreeView::newsep()
1265{
1266 TreeItem *parentItem = 0;
1267 TreeItem *item = (TreeItem*)selectedItem();
1268
1269 if(!item)
1270 {
1271 parentItem = 0;
1272 }
1273 else if(item->isDirectory())
1274 {
1275 parentItem = item;
1276 item = 0;
1277 }
1278 else
1279 {
1280 parentItem = static_cast<TreeItem*>(item->parent());
1281 }
1282
1283 // create the TreeItem
1284 if(parentItem)
1285 parentItem->setExpanded(true);
1286
1287 TreeItem *newItem = createTreeItem(parentItem, item, m_separator, true);
1288
1289 setCurrentItem(newItem);
1290 setLayoutDirty(parentItem);
1291}
1292
1293void TreeView::cut()
1294{
1295 copy( true );
1296
1297 // Select new current item
1298 // TODO: is this completely redundant?
1299 setCurrentItem(currentItem());
1300}
1301
1302void TreeView::copy()
1303{
1304 copy( false );
1305}
1306
1307void TreeView::copy( bool cutting )
1308{
1309 TreeItem *item = (TreeItem*)selectedItem();
1310
1311 // nil selected? -> nil to copy
1312 if (item == 0) return;
1313
1314 if (cutting)
1315 setLayoutDirty((TreeItem*)item->parent());
1316
1317 // clean up old stuff
1318 cleanupClipboard();
1319
1320 // is item a folder or a file?
1321 if(item->isDirectory())
1322 {
1323 QString folder = item->directory();
1324 if (cutting)
1325 {
1326 // Place in clipboard
1327 m_clipboard = MOVE_FOLDER;
1328 m_clipboardFolderInfo = item->folderInfo();
1329
1330 del(item, false);
1331 }
1332 else
1333 {
1334 // Place in clipboard
1335 m_clipboard = COPY_FOLDER;
1336 m_clipboardFolderInfo = item->folderInfo();
1337 }
1338 }
1339 else if (item->isEntry())
1340 {
1341 if (cutting)
1342 {
1343 // Place in clipboard
1344 m_clipboard = MOVE_FILE;
1345 m_clipboardEntryInfo = item->entryInfo();
1346
1347 del(item, false);
1348 }
1349 else
1350 {
1351 // Place in clipboard
1352 m_clipboard = COPY_FILE;
1353 m_clipboardEntryInfo = item->entryInfo();
1354 }
1355 }
1356 else
1357 {
1358 // Place in clipboard
1359 m_clipboard = COPY_SEPARATOR;
1360 if (cutting)
1361 del(item, false);
1362 }
1363
1364 m_ac->action(PASTE_ACTION_NAME)->setEnabled(true);
1365}
1366
1367
1368void TreeView::paste()
1369{
1370 TreeItem *parentItem = 0;
1371 TreeItem *item = (TreeItem*)selectedItem();
1372
1373 // nil selected? -> nil to paste to
1374 if (item == 0) return;
1375
1376 // is there content in the clipboard?
1377 if (!m_clipboard) return;
1378
1379 // get destination folder
1380 QString folder;
1381
1382 if(item->isDirectory())
1383 {
1384 parentItem = item;
1385 item = 0;
1386 folder = parentItem->directory();
1387 }
1388 else
1389 {
1390 parentItem = static_cast<TreeItem*>(item->parent());
1391 folder = parentItem ? parentItem->directory() : QString();
1392 }
1393
1394 MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1395 int command = m_clipboard;
1396 if ((command == COPY_FOLDER) || (command == MOVE_FOLDER))
1397 {
1398 MenuFolderInfo *folderInfo = m_clipboardFolderInfo;
1399 if (command == COPY_FOLDER)
1400 {
1401 // Ugh.. this is hard :)
1402 // * Create new .directory file
1403 // Add
1404 }
1405 else if (command == MOVE_FOLDER)
1406 {
1407 // Move menu
1408 QString oldFolder = folderInfo->fullId;
1409 QString folderName = folderInfo->id;
1410 QString newFolder = m_menuFile->uniqueMenuName(folder, folderName, parentFolderInfo->existingMenuIds());
1411 folderInfo->id = newFolder;
1412
1413 // Add file to menu
1414 // m_menuFile->moveMenu(oldFolder, folder + newFolder);
1415 m_menuFile->pushAction(MenuFile::MOVE_MENU, oldFolder, folder + newFolder);
1416
1417 // Make sure caption is unique
1418 QString newCaption = parentFolderInfo->uniqueMenuCaption(folderInfo->caption);
1419 if (newCaption != folderInfo->caption)
1420 {
1421 folderInfo->setCaption(newCaption);
1422 }
1423 // create the TreeItem
1424 if(parentItem)
1425 parentItem->setExpanded(true);
1426
1427 // update fileInfo data
1428 folderInfo->fullId = parentFolderInfo->fullId + folderInfo->id;
1429 folderInfo->setInUse(true);
1430 parentFolderInfo->add(folderInfo);
1431
1432 TreeItem *newItem = createTreeItem(parentItem, item, folderInfo);
1433
1434 setCurrentItem(newItem);
1435 }
1436
1437 m_clipboard = COPY_FOLDER; // Next one copies.
1438 }
1439 else if ((command == COPY_FILE) || (command == MOVE_FILE))
1440 {
1441 MenuEntryInfo *entryInfo = m_clipboardEntryInfo;
1442 QString menuId;
1443
1444 if (command == COPY_FILE)
1445 {
1446 // Need to copy file and then add it
1447 KDesktopFile *df = copyDesktopFile(entryInfo, &menuId, &m_newMenuIds); // Duplicate
1448
1449 KService::Ptr s(new KService(df));
1450 s->setMenuId(menuId);
1451 entryInfo = new MenuEntryInfo(s, df);
1452
1453 QString oldCaption = entryInfo->caption;
1454 QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption, oldCaption);
1455 entryInfo->setCaption(newCaption);
1456 }
1457 else if (command == MOVE_FILE)
1458 {
1459 menuId = entryInfo->menuId();
1460 m_clipboard = COPY_FILE; // Next one copies.
1461
1462 QString oldCaption = entryInfo->caption;
1463 QString newCaption = parentFolderInfo->uniqueItemCaption(oldCaption);
1464 entryInfo->setCaption(newCaption);
1465 entryInfo->setInUse(true);
1466 }
1467 // Add file to menu
1468 // m_menuFile->addEntry(folder, menuId);
1469 m_menuFile->pushAction(MenuFile::ADD_ENTRY, folder, menuId);
1470
1471 // create the TreeItem
1472 if(parentItem)
1473 parentItem->setExpanded(true);
1474
1475 // update fileInfo data
1476 parentFolderInfo->add(entryInfo);
1477
1478 TreeItem *newItem = createTreeItem(parentItem, item, entryInfo, true);
1479
1480 setCurrentItem(newItem);
1481 }
1482 else
1483 {
1484 // create separator
1485 if(parentItem)
1486 parentItem->setExpanded(true);
1487
1488 TreeItem *newItem = createTreeItem(parentItem, item, m_separator, true);
1489
1490 setCurrentItem(newItem);
1491 }
1492 setLayoutDirty(parentItem);
1493}
1494
1495/**
1496 * This slot is called from the signal mapper to sort children contained in an item.
1497 * This item is determinated according to the chosen sort type.
1498 *
1499 * @brief Determine which item is to sort, and do it.
1500 * @param sortCmd Sort type.
1501 */
1502void TreeView::sort(const int sortCmd)
1503{
1504 // determine the chosen sort type and the selected item
1505 SortType sortType = (SortType) sortCmd;
1506 TreeItem *itemToSort;
1507 if (sortType == SortByName || sortType == SortByDescription) {
1508 itemToSort = static_cast<TreeItem*>(selectedItem());
1509 } else if (sortType == SortAllByDescription) {
1510 sortType = SortByDescription;
1511 itemToSort = static_cast<TreeItem*>(invisibleRootItem());
1512 } else /* if (sortType == SortAllByName) */ {
1513 sortType = SortByName;
1514 itemToSort = static_cast<TreeItem*>(invisibleRootItem());
1515 }
1516
1517 // proceed to the sorting
1518 sortItem(itemToSort, sortType);
1519}
1520
1521/**
1522 * Sort children of the given item, according to the sort type.
1523 * The sorting is done on children groups, splited by separator items.
1524 *
1525 * @brief Sort item children.
1526 * @param item Item to sort.
1527 * @param sortType Sort type.
1528 */
1529void TreeView::sortItem(TreeItem *item, const SortType& sortType)
1530{
1531 // sort the selected item only if contains children
1532 if ( (!item->isDirectory()) || (item->childCount() == 0) ) {
1533 return;
1534 }
1535
1536 // remove contained children
1537 QList<QTreeWidgetItem*> children = item->takeChildren();
1538
1539 // sort children groups, splited by separator items
1540 QList<QTreeWidgetItem*>::iterator startIt = children.begin();
1541 QList<QTreeWidgetItem*>::iterator currentIt = children.begin();
1542 while (currentIt != children.end()) {
1543 TreeItem *child = static_cast<TreeItem*>(*currentIt);
1544 // if it's a separator, sort previous items and continue on following items
1545 if (child->isSeparator() && startIt != currentIt) {
1546 sortItemChildren(startIt, currentIt, sortType);
1547 startIt = currentIt + 1;
1548 }
1549 ++currentIt;
1550 }
1551 sortItemChildren(startIt, currentIt, sortType);
1552
1553 // insert sorted children in the tree
1554 item->addChildren(children);
1555 foreach (QTreeWidgetItem *child, children) {
1556 // recreate item widget for separators
1557 TreeItem *treeItem = static_cast<TreeItem*>(child);
1558 if (treeItem->isSeparator()) {
1559 setItemWidget(treeItem, 0, new SeparatorWidget);
1560 }
1561
1562 // try to sort sub-children
1563 sortItem(static_cast<TreeItem*>(child), sortType);
1564 }
1565
1566 // flag current item as dirty
1567 TreeItem *itemToFlagAsDirty = item;
1568 // if tree root item, set the entire layout as dirty
1569 if (item == invisibleRootItem()) {
1570 itemToFlagAsDirty = 0;
1571 }
1572 setLayoutDirty(itemToFlagAsDirty);
1573}
1574
1575/**
1576 * Sort a children range defined with two list iterators, according to the sort type.
1577 *
1578 * @brief Sort a children range.
1579 * @param begin First child iterator.
1580 * @param end Last child iterator (exclusive, pointed child won't be affected).
1581 * @param sortType Sort type.
1582 */
1583void TreeView::sortItemChildren(const QList<QTreeWidgetItem*>::iterator& begin, const QList<QTreeWidgetItem*>::iterator& end, const SortType& sortType)
1584{
1585 // sort by name
1586 if (sortType == SortByName) {
1587 qSort(begin, end, TreeItem::itemNameLessThan);
1588 }
1589 // sort by description
1590 else if (sortType == SortByDescription) {
1591 qSort(begin, end, TreeItem::itemDescriptionLessThan);
1592 }
1593}
1594
1595/**
1596 * @brief Move up the selected item.
1597 */
1598void TreeView::moveUpItem() {
1599 moveUpOrDownItem(true);
1600}
1601
1602/**
1603 * @brief Move down the selected item.
1604 */
1605void TreeView::moveDownItem() {
1606 moveUpOrDownItem(false);
1607}
1608
1609/**
1610 * Move the selected item on desired direction (up or down).
1611 *
1612 * @brief Move up/down the selected item.
1613 * @param isMovingUpAction True to move up, false to move down.
1614 */
1615void TreeView::moveUpOrDownItem(bool isMovingUpAction)
1616{
1617 // get the selected item and its parent
1618 TreeItem *sourceItem = static_cast<TreeItem*>(selectedItem());
1619 if (!sourceItem)
1620 return;
1621 TreeItem *parentItem = getParentItem(sourceItem);
1622
1623 // get selected item index
1624 int sourceItemIndex = parentItem->indexOfChild(sourceItem);
1625
1626 // find the second item to swap
1627 TreeItem *destItem = 0;
1628 int destIndex;
1629 if (isMovingUpAction) {
1630 destIndex = sourceItemIndex - 1;
1631 destItem = static_cast<TreeItem*>(parentItem->child(destIndex));
1632 }
1633 else {
1634 destIndex = sourceItemIndex + 1;
1635 destItem = static_cast<TreeItem*>(parentItem->child(destIndex));
1636 }
1637
1638 // swap items
1639 parentItem->removeChild(sourceItem);
1640 parentItem->insertChild(destIndex, sourceItem);
1641
1642 // recreate item widget for separators
1643 if (sourceItem->isSeparator()) {
1644 setItemWidget(sourceItem, 0, new SeparatorWidget);
1645 }
1646 if (destItem->isSeparator()) {
1647 setItemWidget(destItem, 0, new SeparatorWidget);
1648 }
1649
1650 // set the focus on the source item
1651 setCurrentItem(sourceItem);
1652
1653 // flag parent item as dirty (if the parent is the root item, set the entire layout as dirty)
1654 if (parentItem == invisibleRootItem()) {
1655 parentItem = 0;
1656 }
1657 setLayoutDirty(parentItem);
1658}
1659
1660/**
1661 * For a given item, return its parent. For top items, return the invisible root item.
1662 *
1663 * @brief Get the parent item.
1664 * @param item Item.
1665 * @return Parent item.
1666 */
1667TreeItem* TreeView::getParentItem(QTreeWidgetItem *item) const
1668{
1669 QTreeWidgetItem *parentItem = item->parent();
1670 if (!parentItem) {
1671 parentItem = invisibleRootItem();
1672 }
1673 return static_cast<TreeItem*>(parentItem);
1674}
1675
1676void TreeView::del()
1677{
1678 TreeItem *item = (TreeItem*)selectedItem();
1679
1680 // nil selected? -> nil to delete
1681 if (item == 0) return;
1682
1683 del(item, true);
1684
1685 // Select new current item
1686 // TODO: is this completely redundant?
1687 setCurrentItem(currentItem());
1688}
1689
1690void TreeView::del(TreeItem *item, bool deleteInfo)
1691{
1692 TreeItem *parentItem = static_cast<TreeItem*>(item->parent());
1693 // is file a .directory or a .desktop file
1694 if(item->isDirectory())
1695 {
1696 if ( KMessageBox::warningYesNo(this, i18n("All submenus of '%1' will be removed. Do you want to continue?", item->name() ) ) == KMessageBox::No )
1697 return;
1698
1699 MenuFolderInfo *folderInfo = item->folderInfo();
1700
1701 // Remove MenuFolderInfo
1702 MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1703 parentFolderInfo->take(folderInfo);
1704 folderInfo->setInUse(false);
1705
1706 if (m_clipboard == COPY_FOLDER && (m_clipboardFolderInfo == folderInfo))
1707 {
1708 // Copy + Del == Cut
1709 m_clipboard = MOVE_FOLDER; // Clipboard now owns folderInfo
1710
1711 }
1712 else
1713 {
1714 if (folderInfo->takeRecursive(m_clipboardFolderInfo))
1715 m_clipboard = MOVE_FOLDER; // Clipboard now owns m_clipboardFolderInfo
1716
1717 if (deleteInfo)
1718 delete folderInfo; // Delete folderInfo
1719 }
1720
1721 // Remove from menu
1722 // m_menuFile->removeMenu(item->directory());
1723 m_menuFile->pushAction(MenuFile::REMOVE_MENU, item->directory(), QString());
1724
1725 // Remove tree item
1726 delete item;
1727 }
1728 else if (item->isEntry())
1729 {
1730 MenuEntryInfo *entryInfo = item->entryInfo();
1731 QString menuId = entryInfo->menuId();
1732
1733 // Remove MenuFolderInfo
1734 MenuFolderInfo *parentFolderInfo = parentItem ? parentItem->folderInfo() : m_rootFolder;
1735 parentFolderInfo->take(entryInfo);
1736 entryInfo->setInUse(false);
1737
1738 if (m_clipboard == COPY_FILE && (m_clipboardEntryInfo == entryInfo))
1739 {
1740 // Copy + Del == Cut
1741 m_clipboard = MOVE_FILE; // Clipboard now owns entryInfo
1742 }
1743 else
1744 {
1745 if (deleteInfo)
1746 delete entryInfo; // Delete entryInfo
1747 }
1748
1749 // Remove from menu
1750 QString folder = parentItem ? parentItem->directory() : QString();
1751 // m_menuFile->removeEntry(folder, menuId);
1752 m_menuFile->pushAction(MenuFile::REMOVE_ENTRY, folder, menuId);
1753
1754 // Remove tree item
1755 delete item;
1756 }
1757 else
1758 {
1759 // Remove separator
1760 delete item;
1761 }
1762
1763 setLayoutDirty(parentItem);
1764}
1765
1766void TreeView::cleanupClipboard() {
1767 if (m_clipboard == MOVE_FOLDER)
1768 delete m_clipboardFolderInfo;
1769 m_clipboardFolderInfo = 0;
1770
1771 if (m_clipboard == MOVE_FILE)
1772 delete m_clipboardEntryInfo;
1773 m_clipboardEntryInfo = 0;
1774
1775 m_clipboard = 0;
1776}
1777
1778static QStringList extractLayout(QTreeWidget *tree, QTreeWidgetItem *parent)
1779{
1780 QStringList layout;
1781 if (!parent && !tree) {
1782 return layout;
1783 }
1784
1785 bool firstFolder = true;
1786 bool firstEntry = true;
1787 int max = parent ? parent->childCount() : tree->topLevelItemCount();
1788 for (int i = 0; i < max; ++i) {
1789 TreeItem *item = dynamic_cast<TreeItem *>(parent ? parent->child(i) : tree->topLevelItem(i));
1790 if (!item) {
1791 continue;
1792 }
1793
1794 if (item->isDirectory()) {
1795 if (firstFolder) {
1796 firstFolder = false;
1797 layout << ":M"; // Add new folders here...
1798 }
1799 layout << (item->folderInfo()->id);
1800 } else if (item->isEntry()) {
1801 if (firstEntry) {
1802 firstEntry = false;
1803 layout << ":F"; // Add new entries here...
1804 }
1805 layout << (item->entryInfo()->menuId());
1806 } else {
1807 layout << ":S";
1808 }
1809 }
1810
1811 return layout;
1812}
1813
1814void TreeItem::saveLayout(MenuFile *menuFile)
1815{
1816 if (m_layoutDirty) {
1817 QStringList layout = extractLayout(0, this);
1818 menuFile->setLayout(folderInfo()->fullId, layout);
1819 m_layoutDirty = false;
1820 }
1821
1822 for (int i = 0; i < childCount(); ++i) {
1823 TreeItem *item = dynamic_cast<TreeItem *>(child(i));
1824 if (item) {
1825 item->saveLayout(menuFile);
1826 }
1827 }
1828}
1829
1830void TreeView::saveLayout()
1831{
1832 if (m_layoutDirty) {
1833 QStringList layout = extractLayout(this, 0);
1834 m_menuFile->setLayout(m_rootFolder->fullId, layout);
1835 m_layoutDirty = false;
1836 }
1837
1838 for (int i = 0; i < topLevelItemCount(); ++i) {
1839 TreeItem *item = dynamic_cast<TreeItem *>(topLevelItem(i));
1840 if (item) {
1841 item->saveLayout(m_menuFile);
1842 }
1843 }
1844}
1845
1846bool TreeView::save()
1847{
1848 saveLayout();
1849 m_rootFolder->save(m_menuFile);
1850
1851 bool success = m_menuFile->performAllActions();
1852
1853 m_newMenuIds.clear();
1854 m_newDirectoryList.clear();
1855
1856 if (success)
1857 {
1858 KBuildSycocaProgressDialog::rebuildKSycoca(this);
1859 }
1860 else
1861 {
1862 KMessageBox::sorry(this, "<qt>"+i18n("Menu changes could not be saved because of the following problem:")+"<br><br>"+
1863 m_menuFile->error()+"</qt>");
1864 }
1865
1866 sendReloadMenu();
1867
1868 return success;
1869}
1870
1871void TreeView::setLayoutDirty(TreeItem *parentItem)
1872{
1873 if (parentItem)
1874 parentItem->setLayoutDirty();
1875 else
1876 m_layoutDirty = true;
1877}
1878
1879bool TreeView::isLayoutDirty()
1880{
1881 for (int i = 0; i < topLevelItemCount(); ++i) {
1882 TreeItem *item = dynamic_cast<TreeItem *>(topLevelItem(i));
1883 if (!item) {
1884 continue;
1885 }
1886
1887 if (item->isLayoutDirty()) {
1888 return true;
1889 }
1890 }
1891
1892 return false;
1893}
1894
1895bool TreeView::dirty()
1896{
1897 return m_layoutDirty || m_rootFolder->hasDirt() || m_menuFile->dirty() || isLayoutDirty();
1898}
1899
1900void TreeView::findServiceShortcut(const KShortcut&cut, KService::Ptr &service)
1901{
1902 service = m_rootFolder->findServiceShortcut(cut);
1903}
1904
1905void TreeView::restoreMenuSystem()
1906{
1907 if ( KMessageBox::warningYesNo( this, i18n( "Do you want to restore the system menu? Warning: This will remove all custom menus." ) )==KMessageBox::No )
1908 return;
1909 QString kmenueditfile = KStandardDirs::locateLocal("xdgconf-menu", "applications-kmenuedit.menu");
1910 if ( QFile::exists( kmenueditfile ) )
1911 {
1912 if ( !QFile::remove( kmenueditfile ) )
1913 qWarning()<<"Could not delete "<<kmenueditfile;
1914 }
1915
1916 QString xdgdir = KGlobal::dirs()->KStandardDirs::localxdgdatadir();
1917 if ( !KIO::NetAccess::del( QString(xdgdir + "/applications") , this) )
1918 qWarning()<<"Could not delete dir :"<<( xdgdir+"/applications" );
1919 if ( !KIO::NetAccess::del( QString(xdgdir +"/desktop-directories") , this) )
1920 qWarning()<<"Could not delete dir :"<<( xdgdir + "/desktop-directories");
1921
1922 KBuildSycocaProgressDialog::rebuildKSycoca(this);
1923 clear();
1924 cleanupClipboard();
1925 delete m_rootFolder;
1926 delete m_separator;
1927
1928 m_layoutDirty = false;
1929 m_newMenuIds.clear();
1930 m_newDirectoryList.clear();
1931 m_menuFile->restoreMenuSystem(kmenueditfile);
1932
1933 m_rootFolder = new MenuFolderInfo;
1934 m_separator = new MenuSeparatorInfo;
1935
1936 readMenuFolderInfo();
1937 fill();
1938 sendReloadMenu();
1939 emit disableAction();
1940 emit entrySelected(( MenuEntryInfo* ) 0 );
1941}
1942
1943void TreeView::updateTreeView(bool showHidden)
1944{
1945 m_showHidden = showHidden;
1946 clear();
1947 cleanupClipboard();
1948 delete m_rootFolder;
1949 delete m_separator;
1950
1951 m_layoutDirty = false;
1952 m_newMenuIds.clear();
1953 m_newDirectoryList.clear();
1954
1955 m_rootFolder = new MenuFolderInfo;
1956 m_separator = new MenuSeparatorInfo;
1957
1958 readMenuFolderInfo();
1959 fill();
1960 sendReloadMenu();
1961 emit disableAction();
1962 emit entrySelected(( MenuEntryInfo* ) 0 );
1963}
1964
1965void TreeView::sendReloadMenu()
1966{
1967 QDBusMessage message = QDBusMessage::createSignal("/kickoff", "org.kde.plasma", "reloadMenu");
1968 QDBusConnection::sessionBus().send(message);
1969}
1970
1971MenuItemMimeData::MenuItemMimeData(TreeItem *item)
1972 : QMimeData(),
1973 m_item(item)
1974{
1975}
1976
1977TreeItem *MenuItemMimeData::item() const
1978{
1979 return m_item;
1980}
1981
1982QStringList MenuItemMimeData::formats() const
1983{
1984 QStringList formats;
1985 if (!m_item) {
1986 return formats;
1987 }
1988
1989 formats << s_internalMimeType;
1990 return formats;
1991}
1992
1993bool MenuItemMimeData::hasFormat(const QString &mimeType) const
1994{
1995 return m_item && mimeType == s_internalMimeType;
1996}
1997
1998QVariant MenuItemMimeData::retrieveData(const QString &mimeType, QVariant::Type type) const
1999{
2000 Q_UNUSED(type);
2001
2002 if (m_item && mimeType == s_internalMimeType) {
2003 return qVariantFromValue<TreeItem*>(m_item);
2004 }
2005
2006 return QVariant();
2007}
2008
2009