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