1 /****************************************************************************
2 * This file is part of qtFM, a simple, fast file manager.
3 * Copyright (C) 2010,2011,2012 Wittfella
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <http://www.gnu.org/licenses/>
17 *
18 * Contact e-mail: wittfella@qtfm.org
19 *
20 ****************************************************************************/
21 
22 #include <QtGui>
23 #include <QDockWidget>
24 #include <QHeaderView>
25 #include <QMessageBox>
26 #include <QInputDialog>
27 #include <QPushButton>
28 #include <QApplication>
29 #include <QStatusBar>
30 #include <QMenu>
31 #include <QMenuBar>
32 #ifndef NO_DBUS
33 #include <QDBusConnection>
34 #include <QDBusError>
35 #endif
36 #include <fcntl.h>
37 
38 #include <QtConcurrent/QtConcurrent>
39 
40 #include "mainwindow.h"
41 #include "mymodel.h"
42 #include "progressdlg.h"
43 #include "fileutils.h"
44 #include "applicationdialog.h"
45 
46 #include "common.h"
47 
48 #ifdef Q_OS_MAC
49 #include <QStyleFactory>
50 #endif
51 
MainWindow()52 MainWindow::MainWindow()
53 {
54     // setup icon theme search path
55     QStringList iconsPath = QIcon::themeSearchPaths();
56     QString iconsHomeLocal = QString("%1/.local/share/icons").arg(QDir::homePath());
57     QString iconsHome = QString("%1/.icons").arg(QDir::homePath());
58     if (QFile::exists(iconsHomeLocal) && !iconsPath.contains(iconsHomeLocal)) { iconsPath.prepend(iconsHomeLocal); }
59     if (QFile::exists(iconsHome) && !iconsPath.contains(iconsHome)) { iconsPath.prepend(iconsHome); }
60     iconsPath << QString("%1/../share/icons").arg(qApp->applicationDirPath());
61     QIcon::setThemeSearchPaths(iconsPath);
62     qDebug() << "using icon theme search path" << QIcon::themeSearchPaths();
63 
64     // libdisks
65 #ifndef NO_UDISKS
66     disks = new Disks(this);
67     connect(disks, SIGNAL(updatedDevices()), this, SLOT(populateMedia()));
68     connect(disks, SIGNAL(mountpointChanged(QString,QString)), this, SLOT(handleMediaMountpointChanged(QString,QString)));
69     connect(disks, SIGNAL(foundNewDevice(QString)), this, SLOT(handleMediaAdded(QString)));
70     connect(disks, SIGNAL(removedDevice(QString)), this, SLOT(handleMediaRemoved(QString)));
71     connect(disks, SIGNAL(mediaChanged(QString,bool)), this, SLOT(handleMediaChanged(QString,bool)));
72     connect(disks, SIGNAL(deviceErrorMessage(QString,QString)), this, SLOT(handleMediaError(QString,QString)));
73 #endif
74 
75     // dbus service
76 #ifndef NO_DBUS
77     if (QDBusConnection::sessionBus().isConnected()) {
78         if (QDBusConnection::sessionBus().registerService(FM_SERVICE)) {
79             service = new qtfm(this);
80             connect(service, SIGNAL(pathRequested(QString)), this, SLOT(handlePathRequested(QString)));
81             if (!QDBusConnection::sessionBus().registerObject(FM_PATH, service, QDBusConnection::ExportAllSlots)) {
82                 qWarning() << QDBusConnection::sessionBus().lastError().message();
83             }
84         } else { qWarning() << QDBusConnection::sessionBus().lastError().message(); }
85     }
86 #endif
87 
88     // get path from cmd
89     startPath = QDir::currentPath();
90     QStringList args = QApplication::arguments();
91 
92     if(args.count() > 1) {
93         startPath = args.at(1);
94         if (startPath == ".") {
95             startPath = getenv("PWD");
96         } else if (QUrl(startPath).isLocalFile()) {
97             startPath = QUrl(args.at(1)).toLocalFile();
98         }
99     }
100 
101     settings = new QSettings(Common::configFile(), QSettings::IniFormat);
102     if (settings->value("clearCache").toBool()) {
103         qDebug() << "clear cache";
104         Common::removeFileCache();
105         Common::removeFolderCache();
106         Common::removeThumbsCache();
107         settings->setValue("clearCache", false);
108     }
109 
110     // Dark theme
111 #ifdef DEPLOY
112     if (settings->value("darkTheme", true).toBool()) {
113 #else
114     if (settings->value("darkTheme").toBool()) {
115 #endif
116         qApp->setPalette(Common::darkTheme());
117     }
118 
119     // set icon theme
120 #ifdef Q_OS_MAC
121 #ifdef DEPLOY
122     QIcon::setThemeName("Adwaita");
123     qApp->setStyle(QStyleFactory::create("fusion"));
124 #else
125     Common::setupIconTheme(qApp->applicationFilePath());
126 #endif
127 #else
128     Common::setupIconTheme(qApp->applicationFilePath());
129 #endif
130 
131     // Create mime utils
132     mimeUtils = new MimeUtils(this);
133     QString name = settings->value("defMimeAppsFile", MIME_APPS).toString();
134     mimeUtils->setDefaultsFileName(name);
135 
136     // Create filesystem model
137     bool realMime = settings->value("realMimeTypes", true).toBool();
138     modelList = new myModel(realMime, mimeUtils, this);
139     connect(modelList, SIGNAL(reloadDir(QString)), this, SLOT(handleReloadDir(QString)));
140 
141     dockTree = new QDockWidget(tr("Tree"),this,Qt::SubWindow);
142     dockTree->setObjectName("treeDock");
143 
144     tree = new QTreeView(dockTree);
145     dockTree->setWidget(tree);
146     addDockWidget(Qt::LeftDockWidgetArea, dockTree);
147 
148     dockBookmarks = new QDockWidget(tr("Bookmarks"),this,Qt::SubWindow);
149     dockBookmarks->setObjectName("bookmarksDock");
150     dockBookmarks->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
151     bookmarksList = new QListView(dockBookmarks);
152     bookmarksList->setMinimumHeight(24); // Docks get the minimum size from their content widget
153     bookmarksList->setFocusPolicy(Qt::ClickFocus); // Avoid hijacking focus when Tab on Edit Path
154     dockBookmarks->setWidget(bookmarksList);
155     addDockWidget(Qt::LeftDockWidgetArea, dockBookmarks);
156 
157     QWidget *main = new QWidget;
158     mainLayout = new QVBoxLayout(main);
159     mainLayout->setSpacing(0);
160     mainLayout->setContentsMargins(0,0,0,0);
161 
162     stackWidget = new QStackedWidget();
163     QWidget *page = new QWidget();
164     QHBoxLayout *hl1 = new QHBoxLayout(page);
165     hl1->setSpacing(0);
166     hl1->setContentsMargins(0,0,0,0);
167     list = new QListView(page);
168     hl1->addWidget(list);
169     stackWidget->addWidget(page);
170 
171     QWidget *page2 = new QWidget();
172     hl1 = new QHBoxLayout(page2);
173     hl1->setSpacing(0);
174     hl1->setContentsMargins(0,0,0,0);
175     detailTree = new QTreeView(page2);
176     hl1->addWidget(detailTree);
177     stackWidget->addWidget(page2);
178 
179     tabs = new tabBar(modelList->folderIcons);
180 
181     mainLayout->addWidget(stackWidget);
182     mainLayout->addWidget(tabs);
183 
184     setCentralWidget(main);
185 
186     modelTree = new mainTreeFilterProxyModel();
187     modelTree->setSourceModel(modelList);
188     modelTree->setSortCaseSensitivity(Qt::CaseInsensitive);
189 
190     tree->setHeaderHidden(true);
191     tree->setUniformRowHeights(true);
192     tree->setModel(modelTree);
193     tree->hideColumn(1);
194     tree->hideColumn(2);
195     tree->hideColumn(3);
196     tree->hideColumn(4);
197 
198     modelView = new viewsSortProxyModel();
199     modelView->setSourceModel(modelList);
200     modelView->setSortCaseSensitivity(Qt::CaseInsensitive);
201 
202     list->setWrapping(true);
203     list->setWordWrap(true);
204     list->setModel(modelView);
205     ivdelegate = new IconViewDelegate();
206     ildelegate = new IconListDelegate();
207     list->setTextElideMode(Qt::ElideNone);
208     listSelectionModel = list->selectionModel();
209 
210     detailTree->setRootIsDecorated(false);
211     detailTree->setItemsExpandable(false);
212     detailTree->setUniformRowHeights(true);
213     detailTree->setModel(modelView);
214     detailTree->setSelectionModel(listSelectionModel);
215 
216     pathEdit = new QComboBox();
217     pathEdit->setEditable(true);
218     pathEdit->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed);
219     pathEdit->setMinimumWidth(100);
220 
221     status = statusBar();
222     status->setSizeGripEnabled(true);
223     statusName = new QLabel();
224     statusSize = new QLabel();
225     statusDate = new QLabel();
226     status->addPermanentWidget(statusName);
227     status->addPermanentWidget(statusSize);
228     status->addPermanentWidget(statusDate);
229 
230     treeSelectionModel = tree->selectionModel();
231     connect(treeSelectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)),
232             this, SLOT(treeSelectionChanged(QModelIndex,QModelIndex)));
233     tree->setCurrentIndex(modelTree->mapFromSource(modelList->index(startPath)));
234     tree->scrollTo(tree->currentIndex());
235 
236     createActions();
237     createToolBars();
238     createMenus();
239 
240     setWindowIcon(QIcon::fromTheme("qtfm", QIcon(":/images/qtfm.png")));
241     setWindowTitle(APP_NAME);
242 
243     // Create custom action manager
244     customActManager = new CustomActionsManager(settings, actionList, this);
245 
246     // Create bookmarks model
247     modelBookmarks = new bookmarkmodel(modelList->folderIcons);
248     connect(modelBookmarks, SIGNAL(bookmarksChanged()), this, SLOT(handleBookmarksChanged()));
249 
250     // Load settings before showing window
251     loadSettings();
252 
253     // show window
254     show();
255 
256     trashDir = Common::trashDir();
257     ignoreReload = false;
258 
259     qApp->installEventFilter(this);
260 
261     QTimer::singleShot(0, this, SLOT(lateStart()));
262 }
263 //---------------------------------------------------------------------------
264 
265 /**
266  * @brief Initialization
267  */
268 void MainWindow::lateStart() {
269 
270   // Update status panel
271   status->showMessage(Common::getDriveInfo(curIndex.filePath()));
272 
273   // Configure bookmarks list
274   bookmarksList->setDragDropMode(QAbstractItemView::DragDrop);
275   bookmarksList->setDropIndicatorShown(true);
276   bookmarksList->setDefaultDropAction(Qt::MoveAction);
277   bookmarksList->setSelectionMode(QAbstractItemView::ExtendedSelection);
278 
279   // Configure tree view
280   tree->setDragDropMode(QAbstractItemView::DragDrop);
281   tree->setDefaultDropAction(Qt::MoveAction);
282   tree->setDropIndicatorShown(true);
283   tree->setEditTriggers(QAbstractItemView::EditKeyPressed |
284                         QAbstractItemView::SelectedClicked);
285 
286   // Configure detail view
287   detailTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
288   detailTree->setDragDropMode(QAbstractItemView::DragDrop);
289   detailTree->setDefaultDropAction(Qt::MoveAction);
290   detailTree->setDropIndicatorShown(true);
291   detailTree->setEditTriggers(QAbstractItemView::EditKeyPressed |
292                               QAbstractItemView::SelectedClicked);
293 
294   // Configure list view
295   list->setResizeMode(QListView::Adjust);
296   list->setSelectionMode(QAbstractItemView::ExtendedSelection);
297   list->setSelectionRectVisible(true);
298   list->setFocus();
299   list->setEditTriggers(QAbstractItemView::EditKeyPressed |
300                         QAbstractItemView::SelectedClicked);
301 
302   // Clipboard configuration
303   progress = Q_NULLPTR;
304   clipboardChanged();
305 
306   // Completer configuration
307   customComplete = new myCompleter;
308   customComplete->setModel(modelTree);
309   customComplete->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
310   customComplete->setMaxVisibleItems(10);
311   pathEdit->setCompleter(customComplete);
312 
313   // Tabs configuration
314   tabs->setDrawBase(0);
315   tabs->setExpanding(0);
316 
317   // Connect mouse clicks in views
318   if (settings->value("singleClick").toInt() == 1) {
319     connect(list, SIGNAL(clicked(QModelIndex)),
320             this, SLOT(listItemClicked(QModelIndex)));
321     connect(detailTree, SIGNAL(clicked(QModelIndex)),
322             this, SLOT(listItemClicked(QModelIndex)));
323   }
324   if (settings->value("singleClick").toInt() == 2) {
325     connect(list, SIGNAL(clicked(QModelIndex))
326             ,this, SLOT(listDoubleClicked(QModelIndex)));
327     connect(detailTree, SIGNAL(clicked(QModelIndex)),
328             this, SLOT(listDoubleClicked(QModelIndex)));
329   }
330 
331   // Connect list view
332   connect(list, SIGNAL(activated(QModelIndex)),
333           this, SLOT(listDoubleClicked(QModelIndex)));
334 
335   // Connect custom action manager
336   connect(customActManager, SIGNAL(actionMapped(QString)),
337           SLOT(actionMapper(QString)));
338   connect(customActManager, SIGNAL(actionsLoaded()), SLOT(readShortcuts()));
339   connect(customActManager, SIGNAL(actionFinished()), SLOT(clearCutItems()));
340 
341   // Connect path edit
342   connect(pathEdit, SIGNAL(activated(QString)),
343           this, SLOT(pathEditChanged(QString)));
344   connect(customComplete, SIGNAL(activated(QString)),
345           this, SLOT(pathEditChanged(QString)));
346   connect(pathEdit->lineEdit(), SIGNAL(cursorPositionChanged(int,int)),
347           this, SLOT(addressChanged(int,int)));
348 
349   // Connect bookmarks
350   connect(bookmarksList, SIGNAL(activated(QModelIndex)),
351           this, SLOT(bookmarkClicked(QModelIndex)));
352   connect(bookmarksList, SIGNAL(clicked(QModelIndex)),
353           this, SLOT(bookmarkClicked(QModelIndex)));
354   connect(bookmarksList, SIGNAL(pressed(QModelIndex)),
355           this, SLOT(bookmarkPressed(QModelIndex)));
356 
357   // Connect selection
358   connect(QApplication::clipboard(), SIGNAL(changed(QClipboard::Mode)),
359           this, SLOT(clipboardChanged()));
360   connect(detailTree,SIGNAL(activated(QModelIndex)),
361           this, SLOT(listDoubleClicked(QModelIndex)));
362   connect(listSelectionModel,
363           SIGNAL(selectionChanged(const QItemSelection, const QItemSelection)),
364           this, SLOT(listSelectionChanged(const QItemSelection,
365                                           const QItemSelection)));
366 
367   // Connect copy progress
368   connect(this, SIGNAL(copyProgressFinished(int,QStringList)),
369           this, SLOT(progressFinished(int,QStringList)));
370 
371   // Connect bookmark model
372   connect(modelBookmarks,
373           SIGNAL(bookmarkPaste(const QMimeData *, QString, QStringList, bool)), this,
374           SLOT(pasteLauncher(const QMimeData *, QString, QStringList, bool)));
375   connect(modelBookmarks, SIGNAL(rowsInserted(QModelIndex, int, int)),
376           this, SLOT(readShortcuts()));
377   connect(modelBookmarks, SIGNAL(rowsRemoved(QModelIndex, int, int)),
378           this, SLOT(readShortcuts()));
379 
380   // Conect list model
381   connect(modelList,
382           SIGNAL(dragDropPaste(const QMimeData *, QString, Common::DragMode)),
383           this,
384           SLOT(dragLauncher(const QMimeData *, QString, Common::DragMode)));
385 
386   // Connect tabs
387   connect(tabs, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
388   connect(tabs, SIGNAL(dragDropTab(const QMimeData *, QString, QStringList)),
389           this, SLOT(pasteLauncher(const QMimeData *, QString, QStringList)));
390   connect(list, SIGNAL(pressed(QModelIndex)),
391           this, SLOT(listItemPressed(QModelIndex)));
392   connect(detailTree, SIGNAL(pressed(QModelIndex)),
393           this, SLOT(listItemPressed(QModelIndex)));
394 
395   connect(modelList, SIGNAL(thumbUpdate(QString)),
396           this, SLOT(thumbUpdate(QString)));
397 
398   qApp->setKeyboardInputInterval(1000);
399 
400   // Read custom actions
401   QTimer::singleShot(100, customActManager, SLOT(readActions()));
402 
403   // Read defaults
404   QTimer::singleShot(100, mimeUtils, SLOT(generateDefaults()));
405 }
406 //---------------------------------------------------------------------------
407 
408 /**
409  * @brief Loads application settings
410  */
411 void MainWindow::loadSettings(bool wState, bool hState, bool tabState, bool thumbState) {
412 
413   // first run?
414     bool isFirstRun = false;
415     if (!settings->value("firstRun").isValid()) {
416         isFirstRun = true;
417         settings->setValue("firstRun", false);
418     }
419 
420   // fix style
421   setStyleSheet("QToolBar { padding: 0;border:none; }"
422                 /*"QFrame { border:none; }"
423                 "QListView::item,QListView::text,QListView::icon"
424                 "{ border:0px;padding-top:5px;padding-left:5px; }"*/);
425   addressToolBar->setContentsMargins(0,0,5,0);
426 
427   // Restore window state
428   if (wState) {
429       qDebug() << "restore window state";
430       if (!settings->value("windowState").isValid()) { // don't show dock tree/app as default
431           dockTree->hide();
432       }
433       restoreState(settings->value("windowState").toByteArray(), 1);
434       restoreGeometry(settings->value("windowGeo").toByteArray());
435       if (settings->value("windowMax").toBool()) { showMaximized(); }
436   }
437 
438   // Load info whether use real mime types
439   modelList->setRealMimeTypes(settings->value("realMimeTypes", true).toBool());
440 
441   // Load information whether hidden files can be displayed
442   if (hState) {
443       hiddenAct->setChecked(settings->value("hiddenMode", 0).toBool());
444       toggleHidden();
445   }
446 
447   // Remove old bookmarks
448   modelBookmarks->removeRows(0, modelBookmarks->rowCount());
449 
450   // Load bookmarks
451   loadBookmarks();
452 
453   // Set bookmarks
454   firstRunBookmarks(isFirstRun);
455 #ifndef NO_UDISKS
456   populateMedia();
457 #endif
458   bookmarksList->setModel(modelBookmarks);
459   bookmarksList->setResizeMode(QListView::Adjust);
460   bookmarksList->setFlow(QListView::TopToBottom);
461   bookmarksList->setIconSize(QSize(24,24));
462 
463   // Load information whether bookmarks are displayed
464   wrapBookmarksAct->setChecked(settings->value("wrapBookmarks", 0).toBool());
465   bookmarksList->setWrapping(wrapBookmarksAct->isChecked());
466 
467   // Lock information whether layout is locked or not
468   lockLayoutAct->setChecked(settings->value("lockLayout", 1).toBool());
469   toggleLockLayout();
470 
471   // Load zoom settings
472   zoom = settings->value("zoom", 48).toInt();
473   zoomTree = settings->value("zoomTree", 16).toInt();
474   zoomBook = settings->value("zoomBook", 24).toInt();
475   zoomList = settings->value("zoomList", 24).toInt();
476   zoomDetail = settings->value("zoomDetail", 32).toInt();
477   detailTree->setIconSize(QSize(zoomDetail, zoomDetail));
478   tree->setIconSize(QSize(zoomTree, zoomTree));
479   bookmarksList->setIconSize(QSize(zoomBook, zoomBook));
480 
481   // Load information whether thumbnails can be shown
482   if (thumbState) {
483     thumbsAct->setChecked(settings->value("showThumbs", 1).toBool());
484   }
485 
486   // Load view mode
487   detailAct->setChecked(settings->value("viewMode", 0).toBool());
488   iconAct->setChecked(settings->value("iconMode", 1).toBool());
489   toggleDetails();
490 
491   // Restore header of detail tree
492   detailTree->header()->restoreState(settings->value("header").toByteArray());
493   detailTree->setSortingEnabled(1);
494 
495   // Load sorting information and sort
496   currentSortColumn = settings->value("sortBy", 0).toInt();
497   currentSortOrder = (Qt::SortOrder) settings->value("sortOrder", 0).toInt();
498   switch (currentSortColumn) {
499     case 0 : setSortColumn(sortNameAct); break;
500     case 1 : setSortColumn(sortSizeAct); break;
501     case 3 : setSortColumn(sortDateAct); break;
502   }
503   setSortOrder(currentSortOrder);
504   modelView->sort(currentSortColumn, currentSortOrder);
505 
506   // Load terminal command
507   term = settings->value("term", "xterm").toString();
508 
509   // custom actions
510 #ifndef Q_OS_MAC
511   firstRunCustomActions(isFirstRun);
512 #endif
513 
514   // Load information whether tabs can be shown on top
515   if (tabState) {
516       tabsOnTopAct->setChecked(settings->value("tabsOnTop", 0).toBool());
517       tabsOnTop();
518   }
519 
520   // show/hide buttons
521   homeAct->setVisible(settings->value("home_button", true).toBool());
522   newTabAct->setVisible(settings->value("newtab_button", false).toBool());
523   terminalAct->setVisible(settings->value("terminal_button", true).toBool());
524 
525   // path history
526   pathHistory = settings->value("pathHistory", true).toBool();
527 
528   // path in window title
529   showPathInWindowTitle = settings->value("windowTitlePath", true).toBool();
530   if (!showPathInWindowTitle) { setWindowTitle(APP_NAME); }
531 
532   // 'copy of' filename
533   copyXof = settings->value("copyXof", COPY_X_OF).toString();
534   copyXofTS = settings->value("copyXofTS", COPY_X_TS).toString();
535 }
536 
537 void MainWindow::firstRunBookmarks(bool isFirstRun)
538 {
539     if (!isFirstRun) { return; }
540     //qDebug() << "first run, setup default bookmarks";
541     modelBookmarks->addBookmark(tr("Computer"), "/", "", "computer", "", false, false);
542 #ifdef Q_OS_MAC
543     modelBookmarks->addBookmark(tr("Applications"), "/Applications", "", "applications-other", "", false, false);
544 #endif
545     modelBookmarks->addBookmark(tr("Home"), QDir::homePath(), "", "user-home", "", false, false);
546     modelBookmarks->addBookmark(tr("Desktop"), QString("%1/Desktop").arg(QDir::homePath()), "", "user-desktop", "", false, false);
547     //modelBookmarks->addBookmark(tr("Documents"), QString("%1/Documents").arg(QDir::homePath()), "", "text-x-generic", "", false, false);
548     //modelBookmarks->addBookmark(tr("Downloads"), QString("%1/Dowloads").arg(QDir::homePath()), "", "applications-internet", "", false, false);
549     //modelBookmarks->addBookmark(tr("Pictures"), QString("%1/Pictures").arg(QDir::homePath()), "", "image-x-generic", "", false, false);
550     //modelBookmarks->addBookmark(tr("Videos"), QString("%1/Videos").arg(QDir::homePath()), "", "video-x-generic", "", false, false);
551     //modelBookmarks->addBookmark(tr("Music"), QString("%1/Music").arg(QDir::homePath()), "", "audio-x-generic", "", false, false);
552     modelBookmarks->addBookmark(tr("Trash"), QString("%1/.local/share/Trash").arg(QDir::homePath()), "", "user-trash", "", false, false);
553     modelBookmarks->addBookmark("", "", "", "", "", false, false);
554     writeBookmarks();
555 }
556 
557 void MainWindow::loadBookmarks()
558 {
559     //qDebug() << "load bookmarks";
560     settings->beginGroup("bookmarks");
561     foreach (QString key,settings->childKeys()) {
562       QStringList temp(settings->value(key).toStringList());
563       modelBookmarks->addBookmark(temp[0], temp[1], temp[2], temp.last(), "", false, false);
564     }
565     settings->endGroup();
566 }
567 
568 void MainWindow::writeBookmarks()
569 {
570     //qDebug() << "write bookmarks";
571     settings->remove("bookmarks");
572     settings->beginGroup("bookmarks");
573     for (int i = 0; i < modelBookmarks->rowCount(); i++) {
574       if (modelBookmarks->item(i)->data(MEDIA_MODEL).toBool()) { continue; } // ignore media devices
575       QStringList temp;
576       temp << modelBookmarks->item(i)->text()
577            << modelBookmarks->item(i)->data(BOOKMARK_PATH).toString()
578            << modelBookmarks->item(i)->data(BOOKMARKS_AUTO).toString()
579            << modelBookmarks->item(i)->data(BOOKMARK_ICON).toString();
580       QString number = QString("%1").arg(i, 4, 10, QChar('0'));
581       settings->setValue(number, temp);
582     }
583     settings->endGroup();
584 }
585 
586 void MainWindow::handleBookmarksChanged()
587 {
588     //qDebug() << "bookmarks changed, save";
589     QTimer::singleShot(1000, this, SLOT(writeBookmarks()));
590 }
591 
592 void MainWindow::firstRunCustomActions(bool isFirstRun)
593 {
594     if (!isFirstRun) { return; }
595     settings->beginGroup("customActions");
596     int childs = settings->childKeys().size();
597     if (childs>0) { return; }
598 
599     QVector<QStringList> defActions = Common::getDefaultActions();
600     for (int i=0;i<defActions.size();++i) {
601         settings->setValue(QString(i), defActions.at(i));
602     }
603 
604     settings->endGroup();
605     settings->sync();
606 }
607 //---------------------------------------------------------------------------
608 
609 /**
610  * @brief Close event
611  * @param event
612  */
613 void MainWindow::closeEvent(QCloseEvent *event)
614 {
615     // Save settings
616     writeSettings();
617 
618     modelList->cacheInfo();
619     event->accept();
620 }
621 //---------------------------------------------------------------------------
622 
623 /**
624  * @brief Closes main window
625  */
626 void MainWindow::exitAction() {
627   close();
628 }
629 //---------------------------------------------------------------------------
630 
631 void MainWindow::treeSelectionChanged(QModelIndex current, QModelIndex previous)
632 {
633     qDebug() << "treeSelectionChanged";
634     Q_UNUSED(previous)
635 
636     QFileInfo name = modelList->fileInfo(modelTree->mapToSource(current));
637     if (!name.exists()) { return; }
638 
639     curIndex = name;
640     if (showPathInWindowTitle) {
641         if (curIndex.fileName().isEmpty()) { setWindowTitle(curIndex.absolutePath()); }
642         else { setWindowTitle(curIndex.fileName()); }
643     } else {
644         setWindowTitle(APP_NAME);
645     }
646 
647     if (tree->hasFocus() && QApplication::mouseButtons() == Qt::MidButton) {
648         listItemPressed(modelView->mapFromSource(modelList->index(name.filePath())));
649         tabs->setCurrentIndex(tabs->count() - 1);
650         if (currentView == 2) { detailTree->setFocus(Qt::TabFocusReason); }
651         else { list->setFocus(Qt::TabFocusReason); }
652     }
653 
654     if (curIndex.filePath() != pathEdit->itemText(0)) {
655         if (tabs->count() && pathHistory) { tabs->addHistory(curIndex.filePath()); }
656         if (!pathHistory && pathEdit->count()>0) { pathEdit->clear(); }
657         pathEdit->insertItem(0,curIndex.filePath());
658         pathEdit->setCurrentIndex(0);
659     }
660 
661     if (!bookmarksList->hasFocus()) { bookmarksList->clearSelection(); }
662 
663     if (modelList->setRootPath(name.filePath())) { modelView->invalidate(); }
664 
665     //////
666     QModelIndex baseIndex = modelView->mapFromSource(modelList->index(name.filePath()));
667 
668     if (currentView == 2) { detailTree->setRootIndex(baseIndex); }
669     else { list->setRootIndex(baseIndex); }
670 
671     if(tabs->count()) {
672         QString tabText = curIndex.fileName();
673         if (tabText.isEmpty()) { tabText = "/"; }
674         tabs->setTabText(tabs->currentIndex(),tabText);
675         tabs->setTabData(tabs->currentIndex(),curIndex.filePath());
676         tabs->setIcon(tabs->currentIndex());
677     }
678 
679     if(backIndex.isValid()) {
680         listSelectionModel->setCurrentIndex(modelView->mapFromSource(backIndex),QItemSelectionModel::ClearAndSelect);
681         if (currentView == 2) { detailTree->scrollTo(modelView->mapFromSource(backIndex)); }
682         else { list->scrollTo(modelView->mapFromSource(backIndex)); }
683     } else {
684         listSelectionModel->blockSignals(1);
685         listSelectionModel->clear();
686     }
687 
688     listSelectionModel->blockSignals(0);
689     updateGrid();
690     qDebug() << "trigger dirloaded on tree selection changed";
691     QTimer::singleShot(30,this,SLOT(dirLoaded()));
692 }
693 
694 //---------------------------------------------------------------------------
695 void MainWindow::dirLoaded(bool thumbs)
696 {
697 
698     if (backIndex.isValid()) {
699         backIndex = QModelIndex();
700         return;
701     }
702 
703     qDebug() << "dirLoaded triggered, thumbs?" << thumbs;
704     qint64 bytes = 0;
705     QModelIndexList items;
706     bool includeHidden = hiddenAct->isChecked();
707 
708     for (int x = 0; x < modelList->rowCount(modelList->index(pathEdit->currentText())); ++x) {
709         items.append(modelList->index(x,0,modelList->index(pathEdit->currentText())));
710     }
711 
712 
713     foreach (QModelIndex theItem,items) {
714         if (includeHidden || !modelList->fileInfo(theItem).isHidden()) {
715             bytes = bytes + modelList->size(theItem);
716         } else { items.removeOne(theItem); }
717     }
718 
719     QString total;
720 
721     if (!bytes) { total = ""; }
722     else { total = Common::formatSize(bytes); }
723 
724     statusName->clear();
725     statusSize->setText(QString("%1 items").arg(items.count()));
726     statusDate->setText(QString("%1").arg(total));
727 
728     if (thumbsAct->isChecked() && thumbs) { QtConcurrent::run(modelList,&myModel::loadThumbs,items); }
729     updateGrid();
730 }
731 
732 void MainWindow::updateDir()
733 {
734     dirLoaded(false /* dont refresh thumb*/);
735 }
736 
737 void MainWindow::handleReloadDir(const QString &path)
738 {
739     if (ignoreReload) {
740         qDebug() << "ignore reload";
741         return;
742     }
743     ignoreReload = true;
744     qDebug() << "handle reload dir" << path << modelList->getRootPath();
745     if (path != modelList->getRootPath()) { return; }
746     dirLoaded();
747     QTimer::singleShot(500, this, SLOT(enableReload()));
748 }
749 
750 void MainWindow::thumbUpdate(const QString &path)
751 {
752     qDebug() << "thumbupdate" << path << modelList->getRootPath();
753     if (path != modelList->getRootPath()) { return; }
754     refresh(false, false);
755 }
756 
757 //---------------------------------------------------------------------------
758 void MainWindow::listSelectionChanged(const QItemSelection selected, const QItemSelection deselected)
759 {
760     Q_UNUSED(selected)
761     Q_UNUSED(deselected)
762 
763     QModelIndexList items;
764 
765     if (listSelectionModel->selectedRows(0).count()) { items = listSelectionModel->selectedRows(0); }
766     else { items = listSelectionModel->selectedIndexes(); }
767 
768     statusSize->clear();
769     statusDate->clear();
770     statusName->clear();
771 
772     if(items.count() == 0) {
773         curIndex = pathEdit->itemText(0);
774         return;
775     }
776 
777     if (QApplication::focusWidget() != bookmarksList) { bookmarksList->clearSelection(); }
778 
779     curIndex = modelList->fileInfo(modelView->mapToSource(listSelectionModel->currentIndex()));
780 
781     qint64 bytes = 0;
782     int folders = 0;
783     int files = 0;
784 
785     foreach(QModelIndex theItem,items) {
786         if (modelList->isDir(modelView->mapToSource(theItem))) { folders++; }
787         else { files++; }
788         bytes = bytes + modelList->size(modelView->mapToSource(theItem));
789     }
790 
791     QString total,name;
792 
793     if (!bytes) { total = ""; }
794     else { total = Common::formatSize(bytes); }
795 
796     if (items.count() == 1) {
797         QFileInfo file(modelList->filePath(modelView->mapToSource(items.at(0))));
798 
799         name = file.fileName();
800         if (file.isSymLink()) { name = "Link --> " + file.symLinkTarget(); }
801 
802         statusName->setText(name + "   ");
803         statusSize->setText(QString("%1   ").arg(total));
804         statusDate->setText(QString("%1").arg(file.lastModified().toString(Qt::SystemLocaleShortDate)));
805     }
806     else {
807         statusName->setText(total + "   ");
808         if (files) { statusSize->setText(QString("%1 files  ").arg(files)); }
809         if (folders) { statusDate->setText(QString("%1 folders").arg(folders)); }
810     }
811 }
812 
813 //---------------------------------------------------------------------------
814 
815 
816 //---------------------------------------------------------------------------
817 void MainWindow::listItemClicked(QModelIndex current)
818 {
819     if (modelList->filePath(modelView->mapToSource(current)) == pathEdit->currentText()) { return; }
820 
821     Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
822     if (mods == Qt::ControlModifier || mods == Qt::ShiftModifier) { return; }
823     if (modelList->isDir(modelView->mapToSource(current))) {
824         tree->setCurrentIndex(modelTree->mapFromSource(modelView->mapToSource(current)));
825     }
826 }
827 
828 //---------------------------------------------------------------------------
829 void MainWindow::listItemPressed(QModelIndex current)
830 {
831     //middle-click -> open new tab
832     //ctrl+middle-click -> open new instance
833 
834     if (QApplication::mouseButtons() == Qt::MidButton) {
835         if(modelList->isDir(modelView->mapToSource(current))) {
836             if (QApplication::keyboardModifiers() == Qt::ControlModifier) { openFile(); }
837             else { addTab(modelList->filePath(modelView->mapToSource(current))); }
838         } else { openFile(); }
839     }
840 }
841 
842 //---------------------------------------------------------------------------
843 void MainWindow::openTab()
844 {
845     if(curIndex.isDir()) {
846         addTab(curIndex.filePath());
847     } else {
848         addTab(QDir::homePath());
849     }
850 }
851 
852 void MainWindow::openNewTab()
853 {
854     QFileInfo info(curIndex.filePath());
855     if (!info.isDir()) { return; }
856     addTab(curIndex.filePath());
857 }
858 
859 //---------------------------------------------------------------------------
860 int MainWindow::addTab(QString path)
861 {
862     if (tabs->count() == 0) { tabs->addNewTab(pathEdit->currentText(),currentView); }
863     return tabs->addNewTab(path,currentView);
864 }
865 
866 //---------------------------------------------------------------------------
867 void MainWindow::tabsOnTop()
868 {
869     if(tabsOnTopAct->isChecked()) {
870         mainLayout->setDirection(QBoxLayout::BottomToTop);
871         tabs->setShape(QTabBar::RoundedNorth);
872     } else {
873         mainLayout->setDirection(QBoxLayout::TopToBottom);
874         tabs->setShape(QTabBar::RoundedSouth);
875     }
876 }
877 
878 //---------------------------------------------------------------------------
879 void MainWindow::tabChanged(int index)
880 {
881     if (tabs->count() == 0) { return; }
882 
883     pathEdit->clear();
884     pathEdit->addItems(*tabs->getHistory(index));
885 
886     int type = tabs->getType(index);
887     if(currentView != type) {
888         if (type == 2) { detailAct->setChecked(1); }
889         else { detailAct->setChecked(0); }
890         if (type == 1) { iconAct->setChecked(1); }
891         else { iconAct->setChecked(0); }
892         toggleDetails();
893     }
894 
895     if(!tabs->tabData(index).toString().isEmpty()) {
896         tree->setCurrentIndex(modelTree->mapFromSource(modelList->index(tabs->tabData(index).toString())));
897     }
898 }
899 
900 void MainWindow::newWindow()
901 {
902     if (settings->value("clearCache").toBool()) {
903         settings->setValue("clearCache", false); // we don't want the new window to clear our existing cache
904     }
905     writeSettings();
906     QProcess::startDetached(qApp->applicationFilePath());
907 }
908 
909 
910 //---------------------------------------------------------------------------
911 
912 /**
913  * @brief Doubleclick on icon/launcher
914  * @param current
915  */
916 void MainWindow::listDoubleClicked(QModelIndex current) {
917   Qt::KeyboardModifiers mods = QApplication::keyboardModifiers();
918   if (mods == Qt::ControlModifier || mods == Qt::ShiftModifier) {
919     return;
920   }
921 #ifdef Q_OS_MAC
922   if (modelList->isDir(modelView->mapToSource(current)) && !modelList->fileName(modelView->mapToSource(current)).endsWith(".app")) {
923 #else
924   if (modelList->isDir(modelView->mapToSource(current))) {
925 #endif
926     QModelIndex i = modelView->mapToSource(current);
927     tree->setCurrentIndex(modelTree->mapFromSource(i));
928   } else {
929     executeFile(current, 0);
930   }
931 }
932 //---------------------------------------------------------------------------
933 
934 /**
935  * @brief Reaction for change of path edit (location edit)
936  * @param path
937  */
938 void MainWindow::pathEditChanged(QString path) {
939   QString info = path;
940   if (!QFileInfo(path).exists()) { return; }
941   info.replace("~",QDir::homePath());
942   tree->setCurrentIndex(modelTree->mapFromSource(modelList->index(info)));
943 }
944 //---------------------------------------------------------------------------
945 
946 /**
947  * @brief Handle clipboard changes
948  */
949 void MainWindow::clipboardChanged()
950 {
951     qDebug() << "clipboard changed";
952     if (QApplication::clipboard()->mimeData()) {
953         if (QApplication::clipboard()->mimeData()->hasUrls()) {
954             qDebug() << "clipboard has data, enable paste";
955             pasteAct->setEnabled(true);
956             return;
957         }
958     }
959     // clear tmp and disable paste if no mime
960     modelList->clearCutItems();
961     pasteAct->setEnabled(false);
962 }
963 //---------------------------------------------------------------------------
964 
965 /**
966  * @brief Pastes from clipboard
967  */
968 void MainWindow::pasteClipboard() {
969   QString newPath;
970   QStringList cutList;
971 
972   if (curIndex.isDir()) { newPath = curIndex.filePath(); }
973   else { newPath = pathEdit->itemText(0); }
974 
975   // Check list of files that are to be cut
976   QFile tempFile(QDir::tempPath() + "/" + APP + ".temp");
977   if (tempFile.exists()) {
978     tempFile.open(QIODevice::ReadOnly);
979     QDataStream out(&tempFile);
980     out >> cutList;
981     tempFile.close();
982   }
983   pasteLauncher(QApplication::clipboard()->mimeData(), newPath, cutList);
984 }
985 //---------------------------------------------------------------------------
986 
987 /**
988  * @brief Drags data to the new location
989  * @param data data to be pasted
990  * @param newPath path of new location
991  * @param dragMode mode of dragging
992  */
993 void MainWindow::dragLauncher(const QMimeData *data, const QString &newPath,
994                               Common::DragMode dragMode) {
995 
996   // Retrieve urls (paths) of data
997   QList<QUrl> files = data->urls();
998 
999   // get original path
1000   QStringList getOldPath = files.at(0).toLocalFile().split("/", QString::SkipEmptyParts);
1001   QString oldPath;
1002   for (int i=0;i<getOldPath.size()-1;++i) { oldPath.append(QString("/%1").arg(getOldPath.at(i))); }
1003   QString oldDevice = Common::getDeviceForDir(oldPath);
1004   QString newDevice = Common::getDeviceForDir(newPath);
1005 
1006   qDebug() << "oldpath:" << oldDevice << oldPath;
1007   qDebug() << "newpath:" << newDevice << newPath;
1008 
1009   QString extraText;
1010   Common::DragMode currentDragMode = dragMode;
1011   if (oldDevice != newDevice) {
1012       extraText = QString(tr("Source and destination is on a different storage."));
1013       currentDragMode = Common::DM_UNKNOWN;
1014   }
1015 
1016   // If drag mode is unknown then ask what to do
1017   if (currentDragMode == Common::DM_UNKNOWN) {
1018     QMessageBox box;
1019     box.setWindowTitle(tr("Select file action"));
1020     box.setWindowIcon(QIcon::fromTheme("qtfm", QIcon(":/images/qtfm.png")));
1021     box.setIconPixmap(QIcon::fromTheme("dialog-information").pixmap(QSize(32, 32)));
1022     box.setText(tr("<h3>What do you want to do?</h3>"));
1023     if (!extraText.isEmpty()) {
1024         box.setText(QString("%1<p>%2</p>").arg(box.text()).arg(extraText));
1025     }
1026     QAbstractButton *move = box.addButton(tr("Move here"), QMessageBox::ActionRole);
1027     QAbstractButton *copy = box.addButton(tr("Copy here"), QMessageBox::ActionRole);
1028     QAbstractButton *link = box.addButton(tr("Link here"), QMessageBox::ActionRole);
1029     QAbstractButton *canc = box.addButton(QMessageBox::Cancel);
1030     move->setIcon(QIcon::fromTheme("edit-cut"));
1031     copy->setIcon(QIcon::fromTheme("edit-copy"));
1032     link->setIcon(QIcon::fromTheme("insert-link"));
1033     canc->setIcon(QIcon::fromTheme("edit-delete"));
1034 
1035     box.exec();
1036     if (box.clickedButton() == move) {
1037       dragMode = Common::DM_MOVE;
1038     } else if (box.clickedButton() == copy) {
1039       dragMode = Common::DM_COPY;
1040     } else if (box.clickedButton() == link) {
1041       dragMode = Common::DM_LINK;
1042     } else if (box.clickedButton() == canc) {
1043       return;
1044     }
1045     currentDragMode = dragMode;
1046   }
1047 
1048   // If moving is enabled, cut files from the original location
1049   QStringList cutList;
1050   if (currentDragMode == Common::DM_MOVE) {
1051     foreach (QUrl item, files) {
1052       cutList.append(item.path());
1053     }
1054   }
1055 
1056   // Paste launcher (this method has to be called instead of that with 'data'
1057   // parameter, because that 'data' can timeout)
1058   pasteLauncher(files, newPath, cutList, dragMode == Common::DM_LINK);
1059 }
1060 //---------------------------------------------------------------------------
1061 
1062 /**
1063  * @brief Pastes data to the new location
1064  * @param data data to be pasted
1065  * @param newPath path of new location
1066  * @param cutList list of items to remove
1067  */
1068 void MainWindow::pasteLauncher(const QMimeData *data,
1069                                const QString &newPath,
1070                                const QStringList &cutList,
1071                                bool link) {
1072   QList<QUrl> files = data->urls();
1073   if (files.isEmpty()) { return; }
1074   pasteLauncher(files, newPath, cutList, link);
1075 }
1076 //---------------------------------------------------------------------------
1077 
1078 /**
1079  * @brief Pastes files to the new path
1080  * @param files list of files
1081  * @param newPath new path
1082  * @param cutList files to remove from original path
1083  * @param link true if link should be created (default value = false)
1084  */
1085 void MainWindow::pasteLauncher(const QList<QUrl> &files, const QString &newPath,
1086                                const QStringList &cutList, bool link) {
1087 
1088   qDebug() << "pasteLauncher" << files << newPath << cutList << link;
1089   // File no longer exists?
1090   if (!QFile(files.at(0).path()).exists()) {
1091     QString msg = tr("File '%1' no longer exists!").arg(files.at(0).path());
1092     QMessageBox::information(this, tr("No paste for you!"), msg);
1093     pasteAct->setEnabled(false);
1094     return;
1095   }
1096 
1097   // Temporary variables
1098   int replace = 0;
1099   QStringList completeList;
1100   QString baseName = QFileInfo(files.at(0).toLocalFile()).path();
1101 
1102   // Only if not in same directory, otherwise we will do 'Copy(x) of'
1103   if (newPath != baseName) {
1104 
1105     foreach (QUrl file, files) {
1106 
1107       // Merge or replace?
1108       QFileInfo temp(file.toLocalFile());
1109 
1110       if (temp.isDir() && QFileInfo(newPath + QDir::separator() + temp.fileName()).exists()) {
1111         QString msg = QString("<b>%1</b><p>Already exists!<p>What do you want to do?").arg(newPath + QDir::separator() + temp.fileName());
1112         QMessageBox message(QMessageBox::Question, tr("Existing folder"), msg, QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
1113         message.button(QMessageBox::Yes)->setText(tr("Merge"));
1114         message.button(QMessageBox::No)->setText(tr("Replace"));
1115 
1116         int merge = message.exec();
1117         if (merge == QMessageBox::Cancel) { return; }
1118         if (merge == QMessageBox::Yes) {
1119           FileUtils::recurseFolder(temp.filePath(), temp.fileName(), &completeList);
1120         } else {
1121           FileUtils::removeRecurse(newPath, temp.fileName());
1122         }
1123       } else completeList.append(temp.fileName());
1124     }
1125 
1126     // Ask whether replace files if files with same name already exist in
1127     // destination directory
1128     foreach (QString file, completeList) {
1129       QFileInfo temp(newPath + QDir::separator() + file);
1130       if (temp.exists()) {
1131         QFileInfo orig(baseName + QDir::separator() + file);
1132         if (replace != QMessageBox::YesToAll && replace != QMessageBox::NoToAll) {
1133           // TODO: error dispalys only at once
1134           replace = showReplaceMsgBox(temp, orig);
1135         }
1136         if (replace == QMessageBox::Cancel) {
1137           return;
1138         }
1139         if (replace == QMessageBox::Yes || replace == QMessageBox::YesToAll) {
1140           QFile(temp.filePath()).remove();
1141         }
1142       }
1143     }
1144   }
1145 
1146   // If only links should be created, create them and exit
1147   if (link) {
1148       qDebug() << "LINK" << files << newPath;
1149     linkFiles(files, newPath);
1150     return;
1151   }
1152 
1153   // Copy/move files
1154   for (int i=0;i<files.size();++i) {
1155       QString queueFile = files.at(i).fileName();
1156       queueFile.prepend(QString("%1/").arg(newPath));
1157       if (!progressQueue.contains(queueFile)) {
1158           qDebug() << "add to queue" << queueFile;
1159           progressQueue.append(queueFile);
1160       }
1161   }
1162   QString title = cutList.count() == 0 ? tr("Copying...") : tr("Moving...");
1163   if (!progress) {
1164       progress = new myProgressDialog(title);
1165       connect(this, SIGNAL(updateCopyProgress(qint64, qint64, QString)), progress, SLOT(update(qint64, qint64, QString)));
1166   }
1167 
1168   listSelectionModel->clear();
1169   QtConcurrent::run(this, &MainWindow::pasteFiles, files, newPath, cutList);
1170 }
1171 //---------------------------------------------------------------------------
1172 
1173 /**
1174  * @brief Asks user whether replace file 'f1' with another file 'f2'
1175  * @param f1 file to be replaced with f2
1176  * @param f2 file to replace f1
1177  * @return result
1178  */
1179 int MainWindow::showReplaceMsgBox(const QFileInfo &f1, const QFileInfo &f2) {
1180 
1181   // Create message
1182   QString t = tr("<h3>Do you want to replace?</h3><p><b>%1</p><p>Modified: %2<br>"
1183                  "Size: %3 bytes</p><p>with:<p><b>%4</p><p>Modified: %5"
1184                  "<br>Size: %6 bytes</p>");
1185 
1186   // Populate message with data
1187   t = t.arg(f1.filePath()).arg(f1.lastModified().toString()).arg(f1.size())
1188        .arg(f2.filePath()).arg(f2.lastModified().toString()).arg(f2.size());
1189 
1190   // Show message
1191   return QMessageBox::question(Q_NULLPTR, tr("Replace"), t, QMessageBox::Yes
1192                                | QMessageBox::YesToAll | QMessageBox::No
1193                                | QMessageBox::NoToAll | QMessageBox::Cancel);
1194 }
1195 //---------------------------------------------------------------------------
1196 
1197 void MainWindow::progressFinished(int ret,QStringList newFiles)
1198 {
1199     qDebug() << "progressFinished" << ret << newFiles;
1200     qDebug() << "progressQueue" << progressQueue;
1201     for (int i=0;i<newFiles.size();++i) {
1202         if (progressQueue.contains(newFiles.at(i))) {
1203             qDebug() << "remove from queue" << newFiles.at(i);
1204             progressQueue.remove(progressQueue.indexOf(newFiles.at(i)));
1205         }
1206     }
1207     qDebug() << "progressQueue" << progressQueue;
1208     if (progress != Q_NULLPTR) {
1209         progress->setResult(0);
1210         qDebug() << "progressDialog filename" << progress->getFilename();
1211         if (progressQueue.isEmpty()) {
1212             qDebug() << "progress should be closed";
1213             progress->close();
1214             delete progress;
1215             progress = Q_NULLPTR;
1216         }
1217     }
1218 
1219     if (newFiles.count()) {
1220         disconnect(listSelectionModel,SIGNAL(selectionChanged(const QItemSelection, const QItemSelection)),this,SLOT(listSelectionChanged(const QItemSelection, const QItemSelection)));
1221 
1222         qApp->processEvents();              //make sure notifier has added new files to the model
1223 
1224         if (QFileInfo(newFiles.first()).path() == pathEdit->currentText()) { // highlight new files if visible
1225             foreach(QString item, newFiles) {
1226                 listSelectionModel->select(modelView->mapFromSource(modelList->index(item)),QItemSelectionModel::Select);
1227             }
1228         }
1229 
1230         connect(listSelectionModel,SIGNAL(selectionChanged(const QItemSelection, const QItemSelection)),this,SLOT(listSelectionChanged(const QItemSelection, const QItemSelection)));
1231         curIndex.setFile(newFiles.first());
1232 
1233         if (currentView == 2) { detailTree->scrollTo(modelView->mapFromSource(modelList->index(newFiles.first())),QAbstractItemView::EnsureVisible); }
1234         else { list->scrollTo(modelView->mapFromSource(modelList->index(newFiles.first())),QAbstractItemView::EnsureVisible); }
1235 
1236         if (QFile(QDir::tempPath() + QString("/%1.temp").arg(APP)).exists()) { QApplication::clipboard()->clear(); }
1237 
1238         clearCutItems();
1239     }
1240 
1241     if (ret == 1) { QMessageBox::information(this,tr("Failed"),tr("Paste failed...do you have write permissions?")); }
1242     if (ret == 2) { QMessageBox::warning(this,tr("Too big!"),tr("There is not enough space on the destination storage!")); }
1243 }
1244 
1245 //---------------------------------------------------------------------------
1246 
1247 void MainWindow::folderPropertiesLauncher()
1248 {
1249     QModelIndexList selList;
1250     if (focusWidget() == bookmarksList) {
1251         selList.append(modelView->mapFromSource(modelList->index(bookmarksList->currentIndex().data(BOOKMARK_PATH).toString())));
1252     } else if (focusWidget() == list || focusWidget() == detailTree) {
1253         if (listSelectionModel->selectedRows(0).count()) { selList = listSelectionModel->selectedRows(0); }
1254         else { selList = listSelectionModel->selectedIndexes(); }
1255     }
1256 
1257     if (selList.count() == 0) { selList << modelView->mapFromSource(modelList->index(pathEdit->currentText())); }
1258 
1259     QStringList paths;
1260 
1261     foreach (QModelIndex item, selList) {
1262         paths.append(modelList->filePath(modelView->mapToSource(item)));
1263     }
1264 
1265     properties = new PropertiesDialog(paths, modelList);
1266     connect(properties,SIGNAL(propertiesUpdated()),this,SLOT(clearCutItems()));
1267 }
1268 
1269 //---------------------------------------------------------------------------
1270 
1271 /**
1272  * @brief Writes settings into config file
1273  */
1274 void MainWindow::writeSettings() {
1275 
1276   // Write general settings
1277   settings->setValue("viewMode", stackWidget->currentIndex());
1278   settings->setValue("iconMode", iconAct->isChecked());
1279   settings->setValue("zoom", zoom);
1280   settings->setValue("zoomTree", zoomTree);
1281   settings->setValue("zoomBook", zoomBook);
1282   settings->setValue("zoomList", zoomList);
1283   settings->setValue("zoomDetail", zoomDetail);
1284   settings->setValue("sortBy", currentSortColumn);
1285   settings->setValue("sortOrder", currentSortOrder);
1286   settings->setValue("showThumbs", thumbsAct->isChecked());
1287   settings->setValue("hiddenMode", hiddenAct->isChecked());
1288   settings->setValue("lockLayout", lockLayoutAct->isChecked());
1289   settings->setValue("tabsOnTop", tabsOnTopAct->isChecked());
1290   settings->setValue("windowState", saveState(1));
1291   settings->setValue("windowGeo", saveGeometry());
1292   settings->setValue("windowMax", isMaximized());
1293   settings->setValue("header", detailTree->header()->saveState());
1294   settings->setValue("realMimeTypes",  modelList->isRealMimeTypes());
1295 
1296   // Write bookmarks
1297   writeBookmarks();
1298 }
1299 //---------------------------------------------------------------------------
1300 
1301 /**
1302  * @brief Display popup menu
1303  * @param event
1304  */
1305 void MainWindow::contextMenuEvent(QContextMenuEvent * event) {
1306 
1307   // Retreive widget under mouse
1308   QMenu *popup;
1309   QWidget *widget = childAt(event->pos());
1310   //qDebug() << "WIDGET" << widget;
1311 
1312   // Create popup for tab or for status bar
1313   if (widget == tabs) {
1314     popup = new QMenu(this);
1315     popup->addAction(closeTabAct);
1316     popup->exec(event->globalPos());
1317     return;
1318   } else if (widget == status) {
1319     popup = createPopupMenu();
1320     popup->addSeparator();
1321     popup->addAction(lockLayoutAct);
1322     popup->exec(event->globalPos());
1323     return;
1324   } else if (widget == navToolBar) {
1325       qDebug() << "TOOLBAR";
1326       return;
1327   }
1328 
1329   QToolButton *isToolButton = dynamic_cast<QToolButton*>(childAt(event->pos()));
1330   if (isToolButton) {
1331       qDebug() << "TOOLBUTTON";
1332       return;
1333   }
1334 
1335 #ifndef Q_OS_MAC
1336   QMenuBar *isMenuBar = dynamic_cast<QMenuBar*>(childAt(event->pos()));
1337   if (isMenuBar) {
1338       qDebug() << "MENUBAR";
1339       return;
1340   }
1341 #endif
1342 
1343   // Continue with poups for folders and files
1344   QList<QAction*> actions;
1345   popup = new QMenu(this);
1346 
1347   bool isMedia = false;
1348 
1349   if (focusWidget() == list || focusWidget() == detailTree) {
1350 
1351     // Clear selection in bookmarks
1352     bookmarksList->clearSelection();
1353 
1354     // Could be file or folder
1355     if (listSelectionModel->hasSelection()) {
1356 
1357       // Get index of source model
1358       curIndex = modelList->filePath(modelView->mapToSource(listSelectionModel->currentIndex()));
1359 
1360       // File
1361       if (!curIndex.isDir()) {
1362         QString type = modelList->getMimeType(modelList->index(curIndex.filePath()));
1363 
1364         // Add custom actions to the list of actions
1365         QHashIterator<QString, QAction*> i(*customActManager->getActions());
1366         while (i.hasNext()) {
1367           i.next();
1368           qDebug() << "custom action" << i.key() << i.key() << i.value();
1369           if (curIndex.completeSuffix().endsWith(i.key())) { actions.append(i.value()); }
1370         }
1371 
1372         // Add run action or open with default application action
1373         if (curIndex.isExecutable() || curIndex.isBundle() || type.endsWith("appimage") || curIndex.absoluteFilePath().endsWith(".desktop")) {
1374           popup->addAction(runAct);
1375         } else {
1376           popup->addAction(openAct);
1377         }
1378 
1379         // Add open action
1380         /*foreach (QAction* action, actions) {
1381           if (action->text() == "Open") {
1382             popup->addAction(action);
1383             break;
1384           }
1385         }*/
1386 
1387         // Add open with menu
1388 #ifndef Q_OS_MAC
1389         popup->addSeparator();
1390         popup->addMenu(createOpenWithMenu());
1391 #endif
1392         //if (popup->actions().count() == 0) popup->addAction(openAct);
1393 
1394         // Add custom actions that are associated only with this file type
1395         if (!actions.isEmpty()) {
1396           popup->addSeparator();
1397           popup->addActions(actions);
1398           popup->addSeparator();
1399         }
1400 
1401         // Add menus
1402         // TODO: ???
1403         QHashIterator<QString, QMenu*> m(*customActManager->getMenus());
1404         while (m.hasNext()) {
1405           m.next();
1406           if (curIndex.completeSuffix().endsWith(m.key())) { popup->addMenu(m.value()); }
1407         }
1408 
1409         // Add cut/copy/paste/rename actions
1410         popup->addSeparator();
1411         popup->addAction(cutAct);
1412         popup->addAction(copyAct);
1413         popup->addAction(pasteAct);
1414         popup->addSeparator();
1415         popup->addAction(renameAct);
1416         popup->addSeparator();
1417 
1418         // Add custom actions that are associated with all file types
1419         foreach (QMenu* parent, customActManager->getMenus()->values("*")) {
1420           popup->addMenu(parent);
1421         }
1422         actions = (customActManager->getActions()->values("*"));
1423         popup->addActions(actions);
1424         if (customActManager->getActionList()->size()>0) {
1425             popup->addSeparator();
1426         }
1427         if (modelList->getRootPath() != trashDir) {
1428             popup->addAction(trashAct);
1429         }
1430         popup->addAction(deleteAct);
1431         popup->addSeparator();
1432         actions = customActManager->getActions()->values(curIndex.path());    //children of $parent
1433         if (actions.count()) {
1434           popup->addActions(actions);
1435           popup->addSeparator();
1436         }
1437       }
1438       // Folder/directory
1439       else {
1440         //popup->addAction(openAct);
1441         popup->addAction(openInTabAct);
1442         popup->addSeparator();
1443         popup->addAction(addBookmarkAct);
1444         popup->addSeparator();
1445         popup->addAction(cutAct);
1446         popup->addAction(copyAct);
1447         popup->addAction(pasteAct);
1448         popup->addSeparator();
1449         popup->addAction(renameAct);
1450         popup->addSeparator();
1451 
1452         foreach (QMenu* parent, customActManager->getMenus()->values("*")) {
1453           popup->addMenu(parent);
1454         }
1455 
1456         actions = customActManager->getActions()->values("*");
1457         popup->addActions(actions);
1458         if (modelList->getRootPath() != trashDir) {
1459             popup->addAction(trashAct);
1460         }
1461         popup->addAction(deleteAct);
1462         popup->addSeparator();
1463 
1464         foreach (QMenu* parent, customActManager->getMenus()->values("folder")) {
1465           popup->addMenu(parent);
1466         }
1467         actions = customActManager->getActions()->values(curIndex.fileName());   // specific folder
1468         actions.append(customActManager->getActions()->values(curIndex.path())); // children of $parent
1469         actions.append(customActManager->getActions()->values("folder"));        // all folders
1470         if (actions.count()) {
1471           popup->addActions(actions);
1472           popup->addSeparator();
1473         }
1474       }
1475       popup->addAction(folderPropertiesAct);
1476     }
1477     // Whitespace
1478     else {
1479       popup->addAction(backAct);
1480       popup->addAction(upAct);
1481       popup->addAction(homeAct);
1482       popup->addSeparator();
1483       popup->addAction(newDirAct);
1484       popup->addAction(newFileAct);
1485       popup->addSeparator();
1486       if (pasteAct->isEnabled()) {
1487         popup->addAction(pasteAct);
1488         popup->addSeparator();
1489       }
1490       popup->addAction(addBookmarkAct);
1491       popup->addSeparator();
1492 
1493       foreach (QMenu* parent, customActManager->getMenus()->values("folder")) {
1494         popup->addMenu(parent);
1495       }
1496       actions = customActManager->getActions()->values(curIndex.fileName());
1497       actions.append(customActManager->getActions()->values("folder"));
1498       if (actions.count()) {
1499         foreach (QAction*action, actions) {
1500           popup->addAction(action);
1501         }
1502         popup->addSeparator();
1503       }
1504       popup->addAction(folderPropertiesAct);
1505     }
1506   }
1507   // Tree or bookmarks
1508   else {
1509     if (focusWidget() == bookmarksList) {
1510       listSelectionModel->clearSelection();
1511       if (bookmarksList->indexAt(bookmarksList->mapFromGlobal(event->globalPos())).isValid()) {
1512         curIndex = bookmarksList->currentIndex().data(BOOKMARK_PATH).toString();
1513         isMedia = bookmarksList->currentIndex().data(MEDIA_MODEL).toBool();
1514         if (!isMedia) {
1515             popup->addAction(delBookmarkAct);
1516             if (!curIndex.path().isEmpty()) {
1517                 popup->addAction(editBookmarkAct);	//icon
1518             }
1519         } else {
1520             // media actions
1521 #ifndef NO_UDISKS
1522             QString mediaPath = bookmarksList->currentIndex().data(MEDIA_PATH).toString();
1523             if (!mediaPath.isEmpty()) {
1524                 if (!disks->devices[mediaPath]->mountpoint.isEmpty()) { // mounted
1525                     popup->addAction(mediaUnmountAct);
1526                 } else { // unmounted
1527                     if (disks->devices[mediaPath]->isOptical) { popup->addAction(mediaEjectAct); }
1528                 }
1529             }
1530 #endif
1531         }
1532       } else {
1533         bookmarksList->clearSelection();
1534         popup->addAction(addSeparatorAct);	//seperator
1535         popup->addAction(wrapBookmarksAct);
1536       }
1537       popup->addSeparator();
1538     } else {
1539       bookmarksList->clearSelection();
1540       popup->addAction(newDirAct);
1541       popup->addAction(newFileAct);
1542       popup->addAction(newWinAct);
1543       popup->addAction(openTabAct);
1544       popup->addSeparator();
1545       popup->addAction(cutAct);
1546       popup->addAction(copyAct);
1547       popup->addAction(pasteAct);
1548       popup->addSeparator();
1549       popup->addAction(renameAct);
1550       popup->addSeparator();
1551       if (modelList->getRootPath() != trashDir) {
1552         popup->addAction(trashAct);
1553       }
1554       popup->addAction(deleteAct);
1555     }
1556     popup->addSeparator();
1557 
1558     foreach (QMenu* parent, customActManager->getMenus()->values("folder")) {
1559       popup->addMenu(parent);
1560     }
1561     actions = customActManager->getActions()->values(curIndex.fileName());
1562     actions.append(customActManager->getActions()->values(curIndex.path()));
1563     actions.append(customActManager->getActions()->values("folder"));
1564     if (actions.count()) {
1565       foreach (QAction*action, actions) { popup->addAction(action); }
1566       popup->addSeparator();
1567     }
1568     if (!isMedia && !curIndex.path().isEmpty()) { popup->addAction(folderPropertiesAct); }
1569   }
1570 
1571   popup->exec(event->globalPos());
1572   delete popup;
1573 }
1574 //---------------------------------------------------------------------------
1575 
1576 /**
1577  * @brief Creates menu for opening file in selected application
1578  * @return menu
1579  */
1580 QMenu* MainWindow::createOpenWithMenu() {
1581 
1582   qDebug() << "open with";
1583   // Add open with functionality ...
1584   QMenu *openMenu = new QMenu(tr("Open with"));
1585 
1586   // Select action
1587   QAction *selectAppAct = new QAction(tr("Select..."), openMenu);
1588   selectAppAct->setStatusTip(tr("Select application for opening the file"));
1589   //selectAppAct->setIcon(actionIcons->at(18));
1590   connect(selectAppAct, SIGNAL(triggered()), this, SLOT(selectAppForFiles()));
1591 
1592   // Load default applications for current mime
1593   QString mime = mimeUtils->getMimeType(curIndex.filePath());
1594   QStringList appNames = mimeUtils->getDefault(mime);
1595   if (appNames.size()==1 && appNames.at(0).isEmpty() && mime.startsWith("text/")) {
1596       qDebug() << "get fallback apps for text/plain";
1597       appNames = mimeUtils->getDefault("text/plain");
1598   }
1599 
1600   qDebug() << mime << appNames;
1601 
1602   // Create actions for opening
1603   QList<QAction*> defaultApps;
1604   foreach (QString appName, appNames) {
1605     // Skip empty app name
1606     if (appName.isEmpty()) { continue; }
1607 
1608     // find .desktop
1609     QString appDesktopFile = Common::findApplication(qApp->applicationFilePath(), appName);
1610     if (appDesktopFile.isEmpty()) { continue; }
1611 
1612     // Load desktop file for application
1613     DesktopFile df = DesktopFile(appDesktopFile);
1614 
1615     // Create action
1616     QAction* action = new QAction(df.getName(), openMenu);
1617     action->setData(/*df.getExec()*/appDesktopFile);
1618     action->setIcon(FileUtils::searchAppIcon(df));
1619     defaultApps.append(action);
1620 
1621     // TODO: icon and connect
1622     connect(action, SIGNAL(triggered()), SLOT(openInApp()));
1623 
1624     // Add action to menu
1625     openMenu->addAction(action);
1626   }
1627 
1628   // Add open action to menu
1629   if (!defaultApps.isEmpty()) {
1630     openMenu->addSeparator();
1631   }
1632   openMenu->addAction(selectAppAct);
1633   return openMenu;
1634 }
1635 
1636 bool MainWindow::eventFilter(QObject *o, QEvent *e)
1637 {
1638     if (e->type() == QEvent::MouseButtonPress) {
1639         QMouseEvent* me = static_cast<QMouseEvent*>(e);
1640         switch (me->button()) {
1641         case Qt::BackButton:
1642             goBackDir();
1643             break;
1644         default:;
1645         }
1646     }
1647 
1648       if (dynamic_cast<QListView*>(o) != Q_NULLPTR ){
1649         if (e->type()==QEvent::KeyPress) {
1650             QKeyEvent* key = static_cast<QKeyEvent*>(e);
1651             if ( (key->key()==Qt::Key_Tab) ) {
1652                 qDebug()<< "Tab pressed: path completion "<< o ;
1653                 QListView *completionList = dynamic_cast<QListView*>(o);
1654                 // Remove incomplete phrase and replace it with a current index
1655                 QModelIndex index = completionList->currentIndex();
1656                 QString itemText = index.data(Qt::DisplayRole).toString();
1657                 QString currentPath = pathEdit->lineEdit()->text();
1658                 QStringList tempList = currentPath.split("/");
1659                 tempList.takeLast();
1660                 tempList << itemText;
1661                 QString newPath = tempList.join("/");
1662                 pathEdit->lineEdit()->setText(newPath);
1663                 // Force update the Main View
1664                 pathEditChanged(newPath);
1665                 // Enter Edit Mode right after the event system is ready
1666                 QTimer::singleShot(0, pathEdit->lineEdit(), SLOT(setFocus()));
1667                 // Add the trailing / for subsequent completions
1668                 pathEdit->lineEdit()->setText(newPath + QString("/"));
1669             }
1670         }
1671     }
1672     return QMainWindow::eventFilter(o, e);
1673 }
1674 
1675 void MainWindow::refresh(bool modelRefresh, bool loadDir)
1676 {
1677     qDebug() << "refresh" << modelRefresh << loadDir;
1678     if (modelRefresh) {
1679         modelList->refreshItems();
1680         modelList->forceRefresh();
1681     }
1682 
1683     QModelIndex baseIndex = modelView->mapFromSource(modelList->index(pathEdit->currentText()));
1684     if (currentView == 2) { detailTree->setRootIndex(baseIndex); }
1685     else { list->setRootIndex(baseIndex); }
1686 
1687     pathEditChanged(pathEdit->currentText());
1688 
1689     if (loadDir) {
1690         qDebug() << "trigger dirloaded from refresh";
1691         dirLoaded();
1692     }
1693 }
1694 
1695 void MainWindow::enableReload()
1696 {
1697     qDebug() << "enable reload";
1698     ignoreReload = false;
1699 }
1700 //---------------------------------------------------------------------------
1701 
1702 /**
1703  * @brief Selects application for opening file
1704  */
1705 void MainWindow::selectApp() {
1706   // Select application in the dialog
1707   ApplicationDialog *dialog = new ApplicationDialog(true, this);
1708   if (dialog->exec()) {
1709     if (dialog->getCurrentLauncher().compare("") != 0) {
1710       QString appName = dialog->getCurrentLauncher() + ".desktop";
1711       QString desktop = Common::findApplication(qApp->applicationFilePath(), appName);
1712       if (desktop.isEmpty()) { return; }
1713       DesktopFile df = DesktopFile(desktop);
1714       mimeUtils->openInApp(df.getExec(), curIndex, df.isTerminal()?term:"");
1715     }
1716   }
1717 }
1718 
1719 void MainWindow::selectAppForFiles()
1720 {
1721     // Selection
1722     QModelIndexList items;
1723     if (listSelectionModel->selectedRows(0).count()) {
1724       items = listSelectionModel->selectedRows(0);
1725     } else {
1726       items = listSelectionModel->selectedIndexes();
1727     }
1728 
1729     // Files
1730     QStringList files;
1731     foreach (QModelIndex index, items) {
1732       //executeFile(index, 0);
1733       QModelIndex srcIndex = modelView->mapToSource(index);
1734       files << modelList->filePath(srcIndex);
1735     }
1736 
1737     // Select application in the dialog
1738     ApplicationDialog *dialog = new ApplicationDialog(true, this);
1739     if (dialog->exec()) {
1740       if (dialog->getCurrentLauncher().compare("") != 0) {
1741         QString appName = dialog->getCurrentLauncher() + ".desktop";
1742         QString desktop = Common::findApplication(qApp->applicationFilePath(), appName);
1743         if (desktop.isEmpty()) { return; }
1744         DesktopFile df = DesktopFile(desktop);
1745         if (df.getExec().contains("%F") || df.getExec().contains("%U")) { // app suports multiple files
1746             mimeUtils->openFilesInApp(df.getExec(), files, df.isTerminal()?term:"");
1747         } else { // launch new instance for each file
1748             for (int i=0;i<files.size();++i) {
1749                 QFileInfo fileInfo(files.at(i));
1750                 mimeUtils->openInApp(df.getExec(), fileInfo, df.isTerminal()?term:"");
1751             }
1752         }
1753       }
1754     }
1755 }
1756 //---------------------------------------------------------------------------
1757 
1758 /**
1759  * @brief Opens files in application
1760  */
1761 void MainWindow::openInApp()
1762 {
1763     QAction* action = dynamic_cast<QAction*>(sender());
1764     if (!action) { return; }
1765     DesktopFile df = DesktopFile(action->data().toString());
1766     if (df.getExec().isEmpty()) { return; }
1767 
1768     // get selection
1769     QModelIndexList items;
1770     if (listSelectionModel->selectedRows(0).count()) {
1771         items = listSelectionModel->selectedRows(0);
1772     } else {
1773         items = listSelectionModel->selectedIndexes();
1774     }
1775 
1776     // get files and mimes
1777     QStringList fileList;
1778     foreach (QModelIndex index, items) {
1779         QModelIndex srcIndex = modelView->mapToSource(index);
1780         QString filePath = modelList->filePath(srcIndex);
1781         fileList << filePath;
1782     }
1783 
1784     if (df.getExec().contains("%F") || df.getExec().contains("%U")) { // app suports multiple files
1785         mimeUtils->openFilesInApp(df.getExec(), fileList, df.isTerminal()?term:"");
1786     } else { // launch new instance for each file
1787         for (int i=0;i<fileList.size();++i) {
1788             QFileInfo fileInfo(fileList.at(i));
1789             mimeUtils->openInApp(df.getExec(), fileInfo, df.isTerminal()?term:"");
1790         }
1791     }
1792 }
1793 
1794 void MainWindow::updateGrid()
1795 {
1796     if (list->viewMode() != QListView::IconMode) { return; }
1797     qDebug() << "updateGrid";
1798     QFontMetrics fm = fontMetrics();
1799     int textWidth = fm.averageCharWidth() * 17;
1800     int realTextWidth = fm.averageCharWidth() * 14;
1801     int textHeight = fm.lineSpacing() * 3;
1802     QSize grid;
1803     grid.setWidth(qMax(zoom, textWidth));
1804     grid.setHeight(zoom+textHeight);
1805 
1806     QModelIndexList items;
1807     for (int x = 0; x < modelList->rowCount(modelList->index(pathEdit->currentText())); ++x) {
1808         items.append(modelList->index(x,0,modelList->index(pathEdit->currentText())));
1809     }
1810     foreach (QModelIndex theItem,items) {
1811         QString filename = modelList->fileName(theItem);
1812         QRect item(0,0,realTextWidth,grid.height());
1813         QSize txtsize = fm.boundingRect(item, Qt::AlignTop|Qt::AlignHCenter|Qt::TextWordWrap|Qt::TextWrapAnywhere, filename).size();
1814         int newHeight = txtsize.height()+zoom+5+8+4;
1815         if  (txtsize.width()>grid.width()) { grid.setWidth(txtsize.width()); }
1816         if (newHeight>grid.height()) { grid.setHeight(newHeight); }
1817     }
1818     if (list->gridSize() != grid) {
1819         list->setGridSize(grid);
1820     }
1821 }
1822 
1823 //---------------------------------------------------------------------------
1824 /**
1825  * @brief media support
1826  */
1827 #ifndef NO_UDISKS
1828 void MainWindow::populateMedia()
1829 {
1830     QMapIterator<QString, Device*> device(disks->devices);
1831     while (device.hasNext()) {
1832         device.next();
1833         if (mediaBookmarkExists(device.value()->path)>-1) { continue; }
1834         if ((device.value()->isOptical && !device.value()->hasMedia)
1835 #ifndef __FreeBSD__
1836                 || (!device.value()->isOptical && !device.value()->isRemovable)
1837 #endif
1838                 || (!device.value()->isOptical && !device.value()->hasPartition)) { continue; }
1839         modelBookmarks->addBookmark(QString("%1 (%2)").arg(device.value()->name).arg(device.value()->dev),
1840                                     device.value()->mountpoint,
1841                                     "",
1842                                     device.value()->isOptical?"drive-optical":"drive-removable-media",
1843                                     device.value()->path,
1844                                     true,
1845                                     false);
1846     }
1847 }
1848 
1849 void MainWindow::handleMediaMountpointChanged(QString path, QString mountpoint)
1850 {
1851     Q_UNUSED(mountpoint)
1852     if (path.isEmpty()) { return; }
1853     for (int i = 0; i < modelBookmarks->rowCount(); i++) {
1854         if (modelBookmarks->item(i)->data(MEDIA_MODEL).toBool() && modelBookmarks->item(i)->data(MEDIA_PATH).toString() == path) {
1855             modelBookmarks->item(i)->setData(disks->devices[path]->mountpoint, BOOKMARK_PATH);
1856         }
1857     }
1858 }
1859 
1860 int MainWindow::mediaBookmarkExists(QString path)
1861 {
1862     if (path.isEmpty()) { return -1; }
1863     for (int i = 0; i < modelBookmarks->rowCount(); ++i) {
1864         if (modelBookmarks->item(i)->data(MEDIA_MODEL).toBool()
1865                 && modelBookmarks->item(i)->data(MEDIA_PATH).toString() == path) { return i; }
1866     }
1867     return -1;
1868 }
1869 
1870 void MainWindow::handleMediaAdded(QString path)
1871 {
1872     Q_UNUSED(path)
1873     populateMedia();
1874 }
1875 
1876 void MainWindow::handleMediaRemoved(QString path)
1877 {
1878     int bookmark = mediaBookmarkExists(path);
1879     if (bookmark>-1) { modelBookmarks->removeRow(bookmark); }
1880 }
1881 
1882 void MainWindow::handleMediaChanged(QString path, bool present)
1883 {
1884     //qDebug() << "changed" << path << present;
1885     if (path.isEmpty()) { return; }
1886     if (disks->devices[path]->isOptical && !present && mediaBookmarkExists(path)>-1) {
1887         handleMediaRemoved(path);
1888     } else if (disks->devices[path]->isOptical && present && mediaBookmarkExists(path)==-1) {
1889         handleMediaAdded(path);
1890     }
1891 }
1892 
1893 void MainWindow::handleMediaUnmount()
1894 {
1895     //qDebug() << "handle media unmount";
1896     QStandardItem *item = modelBookmarks->itemFromIndex(bookmarksList->currentIndex());
1897     if (item == Q_NULLPTR) { return; }
1898     QString path = item->data(MEDIA_PATH).toString();
1899     if (path.isEmpty()) { return; }
1900     disks->devices[path]->unmount();
1901 }
1902 
1903 void MainWindow::handleMediaEject()
1904 {
1905     //qDebug() << "handle media eject";
1906     QStandardItem *item = modelBookmarks->itemFromIndex(bookmarksList->currentIndex());
1907     if (item == Q_NULLPTR) { return; }
1908     QString path = item->data(MEDIA_PATH).toString();
1909     if (path.isEmpty()) { return; }
1910     disks->devices[path]->eject();
1911 }
1912 
1913 void MainWindow::handleMediaError(QString path, QString error)
1914 {
1915     QMessageBox::warning(this, path, error);
1916 }
1917 #endif
1918 
1919 void MainWindow::clearCache()
1920 {
1921     settings->setValue("clearCache", true);
1922     QMessageBox::information(this, tr("Close window"), tr("Please close window to apply action."));
1923 }
1924 
1925 void MainWindow::handlePathRequested(QString path)
1926 {
1927     qDebug() << "handle service path requested" << path;
1928     if (path == pathEdit->currentText() || path.isEmpty()) { return; }
1929     if (path.contains("/.")) { modelList->setRootPath(path); }
1930     pathEdit->setItemText(0, path);
1931     QTimer::singleShot(100, this, SLOT(slowPathEdit()));
1932 }
1933 
1934 void MainWindow::slowPathEdit()
1935 {
1936     pathEditChanged(pathEdit->currentText());
1937     status->showMessage(Common::getDriveInfo(curIndex.filePath()));
1938 }
1939 //---------------------------------------------------------------------------
1940 
1941 void MainWindow::actionMapper(QString cmd)
1942 {
1943     QModelIndexList selList;
1944     QStringList temp;
1945 
1946     if (focusWidget() == list || focusWidget() == detailTree) {
1947         QFileInfo file = modelList->fileInfo(modelView->mapToSource(listSelectionModel->currentIndex()));
1948 
1949         if (file.isDir()) {
1950             cmd.replace("%n",file.fileName().replace(" ","\\"));
1951         } else {
1952             cmd.replace("%n",file.baseName().replace(" ","\\"));
1953         }
1954 
1955         if (listSelectionModel->selectedRows(0).count()) { selList = listSelectionModel->selectedRows(0); }
1956         else { selList = listSelectionModel->selectedIndexes(); }
1957     }
1958     else {
1959         selList << modelView->mapFromSource(modelList->index(curIndex.filePath()));
1960     }
1961 
1962     cmd.replace("~",QDir::homePath());
1963 
1964 
1965     //process any input tokens
1966     int pos = 0;
1967     while(pos >= 0) {
1968         pos = cmd.indexOf("%i",pos);
1969         if(pos != -1) {
1970             pos += 2;
1971             QString var = cmd.mid(pos,cmd.indexOf(" ",pos) - pos);
1972             QString input = QInputDialog::getText(this,tr("Input"), var, QLineEdit::Normal);
1973             if(input.isNull()) { return; } // cancelled
1974             else { cmd.replace("%i" + var,input); }
1975         }
1976     }
1977 
1978 
1979     foreach(QModelIndex index,selList) {
1980         temp.append(modelList->fileName(modelView->mapToSource(index)).replace(" ","\\"));
1981     }
1982 
1983     cmd.replace("%f",temp.join(" "));
1984 
1985     temp.clear();
1986 
1987     foreach(QModelIndex index,selList) {
1988         temp.append(modelList->filePath(modelView->mapToSource(index)).replace(" ","\\"));
1989     }
1990 
1991     cmd.replace("%F",temp.join(" "));
1992 
1993     customActManager->execAction(cmd, pathEdit->itemText(0));
1994 }
1995 
1996 //---------------------------------------------------------------------------------
1997 void MainWindow::clearCutItems()
1998 {
1999     qDebug() << "clearCutItems";
2000     //this refreshes existing items, sizes etc but doesn't re-sort
2001     modelList->clearCutItems();
2002     modelList->update();
2003 
2004     QModelIndex baseIndex = modelView->mapFromSource(modelList->index(pathEdit->currentText()));
2005 
2006     if (currentView == 2) { detailTree->setRootIndex(baseIndex); }
2007     else { list->setRootIndex(baseIndex); }
2008 
2009     qDebug() << "trigger updateDir from clearCutItems";
2010     QTimer::singleShot(50,this,SLOT(updateDir()));
2011 }
2012