1 //**************************************************************************
2 //   Copyright 2006 - 2018 Martin Koller, kollix@aon.at
3 //
4 //   This program is free software; you can redistribute it and/or modify
5 //   it under the terms of the GNU General Public License as published by
6 //   the Free Software Foundation, version 2 of the License
7 //
8 //**************************************************************************
9 
10 #include <Selector.hxx>
11 
12 #include <kio_version.h>
13 #include <kio/global.h>
14 #include <KIconLoader>
15 #include <KIconEffect>
16 #include <KIconUtils>
17 #include <KLocalizedString>
18 #include <KPropertiesDialog>
19 #include <KFileItem>
20 #include <KApplicationTrader>
21 #include <KActionCollection>
22 #include <KMessageBox>
23 
24 #include <KIO/ApplicationLauncherJob>
25 #include <KIO/OpenUrlJob>
26 #include <KIO/JobUiDelegate>
27 
28 #include <QDir>
29 #include <QPixmap>
30 #include <QDateTime>
31 #include <QCollator>
32 #include <QHeaderView>
33 #include <QMenu>
34 #include <QPointer>
35 
36 #include <iostream>
37 using namespace std;
38 
39 //--------------------------------------------------------------------------------
40 //--------------------------------------------------------------------------------
41 
42 class Model : public QStandardItemModel
43 {
44   public:
Model(Selector * parent)45     explicit Model(Selector *parent) : QStandardItemModel(parent), tree(parent)
46     {
47       const char *lc_collate = ::getenv("LC_COLLATE");
48       if ( lc_collate )
49         collator.setLocale(QLocale(QLatin1String(lc_collate)));
50 
51       collator.setNumericMode(true);
52     }
53 
hasChildren(const QModelIndex & index=QModelIndex ()) const54     bool hasChildren(const QModelIndex &index = QModelIndex()) const override
55     {
56       QStandardItem *item = itemFromIndex(index);
57 
58       if ( !item )
59         return true;  // invisible root
60 
61       return !(item->flags() & Qt::ItemNeverHasChildren);
62     }
63 
64     Selector *tree;
65     QCollator collator;
66 };
67 
68 //--------------------------------------------------------------------------------
69 //--------------------------------------------------------------------------------
70 
71 class ListItem : public QStandardItem
72 {
73   public:
ListItem(QStandardItem * parent,const QString & text,bool dir)74     ListItem(QStandardItem *parent, const QString &text, bool dir)
75       : isDir_(dir), partly(false)
76     {
77       parent->appendRow(this);
78       parent->setChild(row(), 1, new QStandardItem);
79       parent->setChild(row(), 2, new QStandardItem);
80 
81       setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable);
82 
83       if ( !isDir_ )
84         setFlags(flags() | Qt::ItemNeverHasChildren);
85 
86       setCheckState(Qt::Unchecked);
87       setText(0, text, key(text));
88     }
89 
type() const90     int type() const override { return QStandardItem::UserType; }
91 
isOn() const92     bool isOn() const { return checkState() == Qt::Checked; }
setOn(bool on)93     void setOn(bool on) { setCheckState(on ? Qt::Checked : Qt::Unchecked); }
94 
setText(int column,const QString & txt,const QVariant & sortKey)95     void setText(int column, const QString &txt, const QVariant &sortKey)
96     {
97       QStandardItem *item;
98 
99       if ( parent() )
100         item = parent()->child(row(), column);
101       else
102         item = model()->item(row(), column);
103 
104       item->setText(txt);
105       item->setData(sortKey, Qt::UserRole);
106     }
107 
setData(const QVariant & value,int role=Qt::UserRole+1)108     void setData(const QVariant &value, int role = Qt::UserRole + 1) override
109     {
110       if ( role == Qt::CheckStateRole )
111       {
112         if ( value.toInt() != checkState() )
113         {
114           QStandardItem::setData(value, role);
115           stateChanged();
116           return;
117         }
118       }
119 
120       QStandardItem::setData(value, role);
121     }
122 
123     // check if all siblings have the same state as the parent or are partly marked
124     // If not, then the parent must have the partly flag set, otherwise the parents
125     // partly flag can be cleared
recursSiblingsUp()126     void recursSiblingsUp()
127     {
128       if ( !parent() ) return;
129 
130       bool allSame = true, state = static_cast<ListItem*>(parent())->isOn();
131 
132       for (int i = 0; i < parent()->rowCount(); i++)
133       {
134         QStandardItem *item = parent()->child(i);
135 
136         if ( (static_cast<ListItem*>(item)->isOn() != state) || static_cast<ListItem*>(item)->partly )
137         {
138           allSame = false;
139           break;
140         }
141       }
142 
143       // only continue upwards if the parents partly status changes
144       if ( static_cast<ListItem*>(parent())->partly != !allSame )
145       {
146         static_cast<ListItem*>(parent())->partly = !allSame;
147 
148         if ( !allSame )
149           static_cast<ListItem*>(parent())->setForeground(Qt::blue);
150         else
151         {
152           QWidget *w = static_cast<Model *>(model())->tree;
153           static_cast<ListItem*>(parent())->setForeground(w->palette().color(w->foregroundRole()));
154         }
155 
156         static_cast<ListItem*>(parent())->recursSiblingsUp();
157       }
158     }
159 
stateChanged()160     void stateChanged()
161     {
162       recursActivate(isOn());
163       recursSiblingsUp();
164     }
165 
166     // set all children recursively below this to on
recursActivate(bool on)167     void recursActivate(bool on)
168     {
169       partly = false;  // all children will get the same state
170 
171       QWidget *w = static_cast<Model *>(model())->tree;
172       setForeground(w->palette().color(w->foregroundRole()));
173 
174       setOn(on);
175 
176       for (int i = 0; i < rowCount(); i++)
177         static_cast<ListItem*>(child(i))->recursActivate(on);
178     }
179 
isDir() const180     bool isDir() const { return isDir_; }
181 
key(const QString & text) const182     int key(const QString &text) const
183     {
184       bool hidden = text[0] == QLatin1Char('.');
185 
186       // sort directories _always_ first, and hidden before shown
187       if ( isDir_ )
188         return hidden ? 0 : 1;
189       else  // file
190         return hidden ? 2 : 3;
191     }
192 
operator <(const QStandardItem & other_) const193     bool operator<(const QStandardItem &other_) const  override
194     {
195       QTreeView *w = static_cast<Model *>(model())->tree;
196       Qt::SortOrder order = w->header()->sortIndicatorOrder();
197 
198       const ListItem &other = static_cast<const ListItem &>(other_);
199 
200       int myKey = data(Qt::UserRole).toInt();
201       int otherKey = other.data(Qt::UserRole).toInt();
202 
203       if ( myKey != otherKey )
204         return (order == Qt::AscendingOrder) ? (myKey < otherKey) : (myKey > otherKey);
205       else
206       {
207         // don't use localeAwareCompare. QLocale does not use LC_COLLATE QTBUG-29397
208         return static_cast<Model *>(model())->collator.compare(text(), other.text()) < 0;
209       }
210     }
211 
setSize(KIO::filesize_t size)212     void setSize(KIO::filesize_t size)
213     {
214       setText(1, KIO::convertSize(size), size);
215     }
216 
setLastModified(const QDateTime & time)217     void setLastModified(const QDateTime &time)
218     {
219       setText(2, QLocale().toString(time, QLocale::ShortFormat), time);
220     }
221 
setShowHiddenFiles(bool show)222     void setShowHiddenFiles(bool show)
223     {
224       QTreeView *w = static_cast<Model *>(model())->tree;
225 
226       QStandardItem *parentItem = parent() ? parent() : model()->invisibleRootItem();
227 
228       w->setRowHidden(row(), parentItem->index(), show ? false : text()[0] == QLatin1Char('.'));
229 
230       for (int i = 0; i < rowCount(); i++)
231         static_cast<ListItem*>(child(i))->setShowHiddenFiles(show);
232     }
233 
234   private:
235     bool isDir_;
236     bool partly;  // is this an item which is not fully (but partly - some of the children) selected
237 
238 };
239 
240 //--------------------------------------------------------------------------------
241 //--------------------------------------------------------------------------------
242 //--------------------------------------------------------------------------------
243 //--------------------------------------------------------------------------------
244 
Selector(QWidget * parent,KActionCollection * actionCollection)245 Selector::Selector(QWidget *parent, KActionCollection *actionCollection)
246   : QTreeView(parent)
247 {
248   itemModel = new Model(this);
249 
250   setModel(itemModel);
251 
252   itemModel->setSortRole(Qt::UserRole);
253   itemModel->setHorizontalHeaderLabels(QStringList() << i18n("Name") << i18n("Size") << i18n("Last Modified"));
254 
255   setRootIsDecorated(true);
256 
257   // start with / as root node
258   ListItem *item = new ListItem(itemModel->invisibleRootItem(), QStringLiteral("/"), true);
259   QFileInfo info(QStringLiteral("/"));
260   item->setSize(info.size());
261   item->setLastModified(info.lastModified());
262   item->setIcon(QIcon::fromTheme(QStringLiteral("folder")));
263   setExpanded(item->index(), true);
264 
265   fillTree(item, QStringLiteral("/"), false);
266 
267   connect(this, &Selector::expanded, this, &Selector::expandedSlot);
268 
269   minSize = QSize(columnWidth(0) + columnWidth(1), -1);
270   resizeColumnToContents(0);
271   resizeColumnToContents(1);
272   resizeColumnToContents(2);
273 
274   sortByColumn(0, Qt::AscendingOrder);
275 
276   // context menu
277   menu = new QMenu(this);
278   QAction *action;
279 
280   action = KStandardAction::open(this, SLOT(open()), actionCollection);
281   menu->addAction(action);
282 
283   connect(this, &Selector::doubleClicked, this, &Selector::doubleClickedSlot);
284 
285   openWithSubMenu = new QMenu(i18n("Open With"), this);
286   menu->addMenu(openWithSubMenu);
287   connect(openWithSubMenu, &QMenu::aboutToShow, this, &Selector::populateOpenMenu);
288   connect(openWithSubMenu, &QMenu::triggered, this, &Selector::openWith);
289 
290   // just since KF 5.25
291   //deleteFileAction = KStandardAction::deleteFile(this, SLOT(deleteFile()), actionCollection);
292   deleteFileAction = actionCollection->addAction(QStringLiteral("deleteFile"), this, SLOT(deleteFile()));
293   deleteFileAction->setText(i18n("Delete File"));
294   deleteFileAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
295   deleteFileAction->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_Delete));
296   menu->addAction(deleteFileAction);
297 
298   action = actionCollection->addAction(QStringLiteral("properties"), this, SLOT(properties()));
299   action->setText(i18n("Properties..."));
300   menu->addAction(action);
301 }
302 
303 //--------------------------------------------------------------------------------
304 
openHomeDir()305 void Selector::openHomeDir()
306 {
307   const char *home = ::getenv("HOME");
308   if ( home )
309   {
310     QStandardItem *homeItem = findItemByPath(QFile::decodeName(home));
311     if ( homeItem )
312     {
313       setExpanded(homeItem->index(), true);
314       scrollTo(homeItem->index());
315     }
316   }
317 }
318 
319 //--------------------------------------------------------------------------------
320 
minimumSizeHint() const321 QSize Selector::minimumSizeHint() const
322 {
323   return minSize;
324 }
325 
326 //--------------------------------------------------------------------------------
327 
fillTree(ListItem * parent,const QString & path,bool on)328 void Selector::fillTree(ListItem *parent, const QString &path, bool on)
329 {
330   setSortingEnabled(false);
331 
332   const QDir::Filters filter = QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot;
333 
334   QDir dir(path, QString(), QDir::NoSort, filter);
335   const QFileInfoList list = dir.entryInfoList();
336 
337   ListItem *item;
338 
339   for (int i = 0; i < list.count(); i++)
340   {
341     if ( parent )
342       item = new ListItem(parent, list[i].fileName(), list[i].isDir());
343     else
344       item = new ListItem(itemModel->invisibleRootItem(), list[i].fileName(), list[i].isDir());
345 
346     item->setOn(on);
347     item->setSize(list[i].size());
348     item->setLastModified(list[i].lastModified());
349     item->setShowHiddenFiles(showHiddenFiles);
350 
351     if ( item->isDir() )
352     {
353       QDir dir(list[i].absoluteFilePath(), QString(), QDir::NoSort, filter);
354 
355       // symlinked dirs can not be expanded as they are stored as single files in the archive
356       if ( (dir.count() > 0) && !list[i].isSymLink() )
357         ; // can have children
358       else
359         item->setFlags(item->flags() | Qt::ItemNeverHasChildren);
360 
361       static QPixmap folderIcon;
362       static QPixmap folderLinkIcon;
363       static QPixmap folderIconHidden;
364       static QPixmap folderLinkIconHidden;
365 
366       if ( folderIcon.isNull() )  // only get the icons once
367       {
368         KIconEffect effect;
369 
370         folderIcon = QIcon::fromTheme(QStringLiteral("folder")).pixmap(KIconLoader::SizeSmall);
371         folderIconHidden = effect.apply(folderIcon, KIconEffect::DeSaturate, 0, QColor(), true);
372 
373         folderLinkIcon = KIconUtils::addOverlay(folderIcon,
374                                                 QIcon::fromTheme(QStringLiteral("emblem-symbolic-link")),
375                                                 Qt::BottomRightCorner).pixmap(KIconLoader::SizeSmall);
376 
377         folderLinkIconHidden = effect.apply(folderLinkIcon, KIconEffect::DeSaturate, 0, QColor(), true);
378       }
379 
380       item->setIcon(list[i].isSymLink() ?
381                            (list[i].isHidden() ? folderLinkIconHidden : folderLinkIcon)
382                          : (list[i].isHidden() ? folderIconHidden : folderIcon));
383     }
384     else
385     {
386       static QPixmap documentIcon;
387       static QPixmap documentLinkIcon;
388       static QPixmap documentIconHidden;
389       static QPixmap documentLinkIconHidden;
390 
391       if ( documentIcon.isNull() )  // only get the icons once
392       {
393         KIconEffect effect;
394 
395         documentIcon = QIcon::fromTheme(QStringLiteral("text-x-generic")).pixmap(KIconLoader::SizeSmall);
396         documentIconHidden = effect.apply(documentIcon, KIconEffect::DeSaturate, 0, QColor(), true);
397 
398         documentLinkIcon = KIconUtils::addOverlay(documentIcon,
399                                                 QIcon::fromTheme(QStringLiteral("emblem-symbolic-link")),
400                                                 Qt::BottomRightCorner).pixmap(KIconLoader::SizeSmall);
401 
402         documentLinkIconHidden = effect.apply(documentLinkIcon, KIconEffect::DeSaturate, 0, QColor(), true);
403       }
404 
405       item->setIcon(list[i].isSymLink() ?
406                            (list[i].isHidden() ? documentLinkIconHidden : documentLinkIcon)
407                          : (list[i].isHidden() ? documentIconHidden : documentIcon));
408     }
409   }
410   setSortingEnabled(true);
411 }
412 
413 //--------------------------------------------------------------------------------
414 
getPath(QStandardItem * item) const415 QString Selector::getPath(QStandardItem *item) const
416 {
417   if ( !item )
418     return QString();
419   else if ( !item->parent() )
420     return item->text();  // root
421   else if ( item->parent() == itemModel->item(0) )
422     return QStringLiteral("/") + item->text();
423   else
424     return getPath(item->parent()) + QStringLiteral("/") + item->text();
425 }
426 
427 //--------------------------------------------------------------------------------
428 
expandedSlot(const QModelIndex & index)429 void Selector::expandedSlot(const QModelIndex &index)
430 {
431   QStandardItem *item = itemModel->itemFromIndex(index);
432 
433   if ( item->rowCount() ) return;  // already done
434 
435   fillTree(static_cast<ListItem *>(item), getPath(item), static_cast<ListItem *>(item)->isOn());
436 }
437 
438 //--------------------------------------------------------------------------------
439 
getBackupList(QStringList & includes,QStringList & excludes) const440 void Selector::getBackupList(QStringList &includes, QStringList &excludes) const
441 {
442   for (int i = 0; i < itemModel->rowCount(); i++)
443     getBackupLists(itemModel->item(i, 0), includes, excludes);
444 
445   /*
446   cerr << "includes:" << includes.count() << endl;
447   for (QStringList::const_iterator it = includes.begin(); (it != includes.end()); ++it)
448     cerr << *it << endl;
449 
450   cerr << "excludes:" << excludes.count() << endl;
451   for (QStringList::const_iterator it = excludes.begin(); (it != excludes.end()); ++it)
452     cerr << *it << endl;
453 
454   cerr << endl;
455   */
456 }
457 
458 //--------------------------------------------------------------------------------
459 
getBackupLists(QStandardItem * start,QStringList & includes,QStringList & excludes,bool add) const460 void Selector::getBackupLists(QStandardItem *start, QStringList &includes, QStringList &excludes, bool add) const
461 {
462   if ( static_cast<ListItem*>(start)->isOn() )
463   {
464     if ( add )
465       includes.append(getPath(start));  // include it
466 
467     if ( static_cast<ListItem*>(start)->isDir() )
468     {
469       // get excludes from this dir
470       for (int i = 0; i < start->rowCount(); i++)
471       {
472         QStandardItem *item = start->child(i);
473 
474         if ( !static_cast<ListItem*>(item)->isOn() )
475           excludes.append(getPath(item));
476 
477         if ( static_cast<ListItem*>(item)->isDir() )
478           getBackupLists(item, includes, excludes, false);
479       }
480     }
481   }
482   else
483     if ( static_cast<ListItem*>(start)->isDir() )
484     {
485       for (int i = 0; i < start->rowCount(); i++)
486         getBackupLists(start->child(i), includes, excludes);
487     }
488 }
489 
490 //--------------------------------------------------------------------------------
491 
setBackupList(const QStringList & includes,const QStringList & excludes)492 void Selector::setBackupList(const QStringList &includes, const QStringList &excludes)
493 {
494   // clear all current settings
495   for (int i = 0; i < itemModel->rowCount(); i++)
496     static_cast<ListItem*>(itemModel->item(i, 0))->recursActivate(false);
497 
498   for (QStringList::const_iterator it = includes.begin(); (it != includes.end()); ++it)
499   {
500     QStandardItem *item = findItemByPath(*it);
501     if ( item )
502       static_cast<ListItem*>(item)->recursActivate(true);
503   }
504 
505   for (QStringList::const_iterator it = excludes.begin(); (it != excludes.end()); ++it)
506   {
507     QStandardItem *item = findItemByPath(*it);
508     if ( item )
509       static_cast<ListItem*>(item)->setOn(false);
510   }
511 }
512 
513 //--------------------------------------------------------------------------------
514 
findItemByPath(const QString & path)515 QStandardItem *Selector::findItemByPath(const QString &path)
516 {
517   QStringList items = path.split(QLatin1Char('/'), Qt::SkipEmptyParts);
518   QStandardItem *item = itemModel->invisibleRootItem()->child(0);
519 
520   for (int i = 0; i < items.count(); i++)
521   {
522     item = findItem(item, items[i]);
523 
524     if ( !item )
525       return nullptr;
526     else
527     {
528       if ( (i != (items.count() - 1)) &&
529            static_cast<ListItem*>(item)->isDir() && (item->rowCount() == 0) )
530         expandedSlot(item->index());
531     }
532   }
533 
534   return item;
535 }
536 
537 //--------------------------------------------------------------------------------
538 
findItem(QStandardItem * start,const QString & toFind) const539 QStandardItem *Selector::findItem(QStandardItem *start, const QString &toFind) const
540 {
541   for (int i = 0; i < (start ? start->rowCount() : itemModel->rowCount()); i++)
542   {
543     QStandardItem *item = start ? start->child(i) : itemModel->item(i, 0);
544 
545     if ( item->text() == toFind )
546       return item;
547   }
548 
549   return nullptr;
550 }
551 
552 //--------------------------------------------------------------------------------
553 
getSelectedItem() const554 ListItem *Selector::getSelectedItem() const
555 {
556   QModelIndex index = selectionModel()->currentIndex();
557   if ( !index.isValid() )
558     return nullptr;
559 
560   QStandardItem *item = itemModel->itemFromIndex(itemModel->index(index.row(), 0, index.parent()));
561 
562   if ( !item || (item->type() != QStandardItem::UserType) )  // just be safe
563     return nullptr;
564 
565   return static_cast<ListItem *>(item);
566 }
567 
568 //--------------------------------------------------------------------------------
569 
contextMenuEvent(QContextMenuEvent *)570 void Selector::contextMenuEvent(QContextMenuEvent *)
571 {
572   ListItem *item = getSelectedItem();
573 
574   if ( !item )
575     return;
576 
577   bool canDelete = true;
578 
579   if ( item->isDir() )
580   {
581     // only if it's empty
582     canDelete = QDir(getPath(item), QString(), QDir::NoSort,
583                      QDir::AllEntries | QDir::System | QDir::NoDotAndDotDot).count() == 0;
584   }
585 
586   deleteFileAction->setEnabled(canDelete);
587 
588   menu->exec(QCursor::pos());
589 }
590 
591 //--------------------------------------------------------------------------------
592 
deleteFile()593 void Selector::deleteFile()
594 {
595   ListItem *item = getSelectedItem();
596 
597   if ( !item )
598     return;
599 
600   QUrl sourceUrl = QUrl::fromLocalFile(getPath(item));
601 
602   if ( KMessageBox::questionYesNo(this,
603           i18n("Do you really want to delete '%1'?", sourceUrl.path()),
604           i18n("Delete"),
605           KStandardGuiItem::yes(), KStandardGuiItem::no(),
606           QStringLiteral("dontAskAgainDelete")) == KMessageBox::Yes )
607   {
608     QStandardItem *parent = nullptr;
609 
610     if ( item->isDir() )
611     {
612       QDir dir(sourceUrl.path());
613 
614       if ( !dir.removeRecursively() )
615         KMessageBox::error(this, i18n("Could not delete directory '%1'.", sourceUrl.path()));
616       else
617         parent = item->parent();
618     }
619     else
620     {
621       QFile theFile(sourceUrl.path());
622 
623       if ( !theFile.remove(sourceUrl.path()) )
624         KMessageBox::error(this, i18n("Could not delete file '%1'.\nReason: %2", sourceUrl.path(), theFile.errorString()));
625       else
626         parent = item->parent();
627     }
628 
629     if ( parent )
630     {
631       parent->removeRow(item->row());
632 
633       if ( parent->type() == QStandardItem::UserType )
634         static_cast<ListItem *>(parent)->stateChanged();  // make sure removed item is taken into account
635     }
636   }
637 }
638 
639 //--------------------------------------------------------------------------------
640 
properties()641 void Selector::properties()
642 {
643   ListItem *item = getSelectedItem();
644 
645   if ( !item )
646     return;
647 
648   QUrl sourceUrl = QUrl::fromLocalFile(getPath(item));
649 
650   QPointer<KPropertiesDialog> dialog = new KPropertiesDialog(sourceUrl, this);
651   connect(dialog.data(), &KPropertiesDialog::applied, this,
652           [item, dialog]()
653           {
654             // make sure a renamed file is shown with the new name in the tree
655             item->setText(0, dialog->item().name(), item->key(dialog->item().name()));
656           });
657 
658   dialog->exec();
659   delete dialog;
660 }
661 
662 //--------------------------------------------------------------------------------
663 
populateOpenMenu()664 void Selector::populateOpenMenu()
665 {
666   ListItem *item = getSelectedItem();
667 
668   if ( !item )
669     return;
670 
671   QUrl sourceUrl = QUrl::fromLocalFile(getPath(item));
672 
673   qDeleteAll(openWithSubMenu->actions());
674   serviceForName.clear();
675 
676   KFileItem fileItem(sourceUrl);
677   QString mimeType(fileItem.determineMimeType().name());
678 
679   const KService::List services = KApplicationTrader::queryByMimeType(mimeType);
680 
681   for (const KService::Ptr &service : services)
682   {
683     QString text = service->name().replace(QLatin1Char('&'), QStringLiteral("&&"));
684     QAction* action = openWithSubMenu->addAction(text);
685     action->setIcon(QIcon::fromTheme(service->icon()));
686     action->setData(service->name());
687 
688     serviceForName[service->name()] = service;
689   }
690 
691   openWithSubMenu->addSeparator();
692   openWithSubMenu->addAction(i18n("Other Application..."));
693 
694   QAction* action = openWithSubMenu->addAction(i18n("File Manager"));
695   action->setIcon(QIcon::fromTheme(QStringLiteral("folder")));
696   action->setData(QStringLiteral("-"));
697 }
698 
699 //--------------------------------------------------------------------------------
700 
doubleClickedSlot()701 void Selector::doubleClickedSlot()
702 {
703   ListItem *item = getSelectedItem();
704 
705   if ( !item || item->isDir() )
706     return;
707 
708   open();
709 }
710 
711 //--------------------------------------------------------------------------------
712 
open()713 void Selector::open()
714 {
715   ListItem *item = getSelectedItem();
716 
717   if ( !item )
718     return;
719 
720   QUrl sourceUrl = QUrl::fromLocalFile(getPath(item));
721 
722   auto *job = new KIO::OpenUrlJob(sourceUrl, window());
723   job->setRunExecutables(false);
724   job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
725   job->start();
726 }
727 
728 //--------------------------------------------------------------------------------
729 
openWith(QAction * action)730 void Selector::openWith(QAction *action)
731 {
732   ListItem *item = getSelectedItem();
733 
734   if ( !item )
735     return;
736 
737   QUrl sourceUrl = QUrl::fromLocalFile(getPath(item));
738 
739   QString name = action->data().toString();
740 
741   if ( name.isEmpty() ) // Other Application...
742   {
743     KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob();
744     job->setUrls(QList<QUrl>() << sourceUrl);
745     job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
746     job->start();
747     return;
748   }
749 
750   if ( name == QLatin1String("-") )  // File Manager
751   {
752     KIO::OpenUrlJob *job = new KIO::OpenUrlJob(sourceUrl.adjusted(QUrl::RemoveFilename), QStringLiteral("inode/directory"));
753     job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
754     job->start();
755 
756     return;
757   }
758 
759   KService::Ptr service = serviceForName[name];
760   KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service);
761   job->setUrls(QList<QUrl>() << sourceUrl);
762   job->setUiDelegate(new KIO::JobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, this));
763   job->start();
764 }
765 
766 //--------------------------------------------------------------------------------
767 
setShowHiddenFiles(bool show)768 void Selector::setShowHiddenFiles(bool show)
769 {
770   showHiddenFiles = show;
771 
772   for (int i = 0; i < itemModel->invisibleRootItem()->rowCount(); i++)
773     static_cast<ListItem *>(itemModel->item(i, 0))->setShowHiddenFiles(show);
774 }
775 
776 //--------------------------------------------------------------------------------
777