1 //===========================================
2 //  Lumina-DE source code
3 //  Copyright (c) 2015, Ken Moore
4 //  Available under the 3-clause BSD license
5 //  See the LICENSE file for full details
6 //===========================================
7 #include "DirWidget2.h"
8 #include "ui_DirWidget2.h"
9 
10 #include <QActionGroup>
11 #include <QMessageBox>
12 #include <QCursor>
13 #include <QClipboard>
14 #include <QMimeData>
15 #include <QTimer>
16 #include <QInputDialog>
17 #include <QScrollBar>
18 #include <QSettings>
19 #include <QtConcurrent>
20 #include <QFileSystemModel>
21 #include <QCompleter>
22 #include <LuminaOS.h>
23 #include <LuminaXDG.h>
24 #include <LUtils.h>
25 #include <ExternalProcess.h>
26 #include <QFileDialog>
27 
28 #include "../ScrollDialog.h"
29 #include <XDGMime.h>
30 
31 #define DEBUG 0
32 extern bool rootmode;
33 
DirWidget(QString objID,QSettings * settings,QWidget * parent)34 DirWidget::DirWidget(QString objID, QSettings *settings, QWidget *parent) : QWidget(parent), ui(new Ui::DirWidget){
35   ui->setupUi(this); //load the designer file
36   ui->label_rootmode->setVisible(rootmode);
37 
38   ID = objID;
39   //Assemble the toolbar for the widget
40   toolbar = new QToolBar(this);
41     toolbar->setContextMenuPolicy(Qt::CustomContextMenu);
42     toolbar->setFloatable(false);
43     toolbar->setMovable(false);
44     toolbar->setOrientation(Qt::Horizontal);
45     toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
46     //toolbar->setIconSize(QSize(32,32));
47   ui->toolbar_layout->addWidget(toolbar);
48   // - Add the buttons to the toolbar
49   toolbar->addAction(ui->actionBack);
50   toolbar->addAction(ui->actionUp);
51   toolbar->addAction(ui->actionHome);
52   line_dir = new QLineEdit(this);
53     toolbar->addWidget(line_dir);
54     connect(line_dir, SIGNAL(returnPressed()), this, SLOT(dir_changed()) );
55   QActionGroup *columnActionGroup = new QActionGroup(this);
56     toolbar->addAction(ui->actionSingleColumn);
57     ui->actionSingleColumn->setChecked(true);
58     columnActionGroup->addAction(ui->actionSingleColumn);
59     toolbar->addAction(ui->actionDualColumn);
60     columnActionGroup->addAction(ui->actionDualColumn);
61   toolbar->addAction(ui->actionMenu);
62   this->settings = settings;
63   //Add the browser widgets
64   RCBW = 0; //right column browser is unavailable initially
65   BW = new BrowserWidget("", this);
66   ui->browser_layout->addWidget(BW);
67   connect(BW, SIGNAL(dirChange(QString, bool)), this, SLOT(currentDirectoryChanged(QString)) );
68   connect(BW, SIGNAL(itemsActivated()), this, SLOT(runFiles()) );
69   connect(BW, SIGNAL(DataDropped(QString, QStringList)), this, SIGNAL(PasteFiles(QString, QStringList)) );
70   connect(BW, SIGNAL(contextMenuRequested()), this, SLOT(OpenContextMenu()) );
71   connect(BW, SIGNAL(updateDirectoryStatus(QString)), this, SLOT(dirStatusChanged(QString)) );
72   connect(BW, SIGNAL(hasFocus(QString)), this, SLOT(setCurrentBrowser(QString)) );
73 
74   // Create treeviewpane QFileSystemModel model and populate
75   QString folderTreePath = QDir::rootPath();
76   dirtreeModel = new QFileSystemModel(this);
77   dirtreeModel->setFilter(QDir::NoDotAndDotDot | QDir::AllDirs);      // remove extraneous dirs
78   dirtreeModel->setRootPath(folderTreePath);
79   ui->folderViewPane->setModel(dirtreeModel);
80   ui->splitter->setSizes( QList<int>() << this->width()/4 << 3*this->width()/4);
81   ui->folderViewPane->setHeaderHidden(true);
82   ui->folderViewPane->resizeColumnToContents(0);
83   ui->folderViewPane->setColumnHidden(1, true);
84   ui->folderViewPane->setColumnHidden(2, true);
85   ui->folderViewPane->setColumnHidden(3, true);
86 
87   //Now update the rest of the UI
88   canmodify = false; //initial value
89   contextMenu = new QMenu(this);
90   cNewMenu = cOpenMenu = cFModMenu = cFViewMenu = cOpenWithMenu = cArchiveMenu =0; //not created yet
91   connect(contextMenu, SIGNAL(aboutToShow()), this, SLOT(UpdateContextMenu()) );
92   connect(ui->splitter, SIGNAL(splitterMoved(int,int)), this, SLOT(splitterMoved()) );
93 
94   UpdateIcons();
95   UpdateText();
96   createShortcuts();
97   createMenus();
98   line_dir->setCompleter(new QCompleter(dirtreeModel, this));
99 }
100 
~DirWidget()101 DirWidget::~DirWidget(){
102   //stopload = true; //just in case another thread is still loading/running
103 }
104 
setFocusLineDir()105 void DirWidget::setFocusLineDir() {
106   line_dir->setFocus();
107   line_dir->selectAll();
108 }
109 
cleanup()110 void DirWidget::cleanup(){
111   //stopload = true; //just in case another thread is still loading/running
112   //if(thumbThread.isRunning()){ thumbThread.waitForFinished(); } //this will stop really quickly with the flag set
113 }
114 
ChangeDir(QString dirpath)115 void DirWidget::ChangeDir(QString dirpath){
116   //stopload = true; //just in case it is still loading
117   //emit LoadDirectory(ID, dirpath);
118   //qDebug() << "ChangeDir:" << dirpath;
119   currentBrowser()->changeDirectory(dirpath);
120 }
121 
setDirCompleter(QCompleter * comp)122 void DirWidget::setDirCompleter(QCompleter *comp){
123   line_dir->setCompleter(comp);
124 }
125 
id()126 QString DirWidget::id(){
127   return ID;
128 }
129 
currentDir()130 QString DirWidget::currentDir(){
131   return currentBrowser()->currentDirectory();
132 }
133 
setShowDetails(bool show)134 void DirWidget::setShowDetails(bool show){
135   BW->showDetails(show);
136   if(RCBW!=0){ RCBW->showDetails(show); }
137 }
138 
showHidden(bool show)139 void DirWidget::showHidden(bool show){
140   BW->showHiddenFiles(show);
141   if(RCBW!=0){ RCBW->showHiddenFiles(show); }
142   //Also make sure the tree model is showing hidden files as needed
143   if(show){ dirtreeModel->setFilter(QDir::NoDotAndDotDot | QDir::Hidden | QDir::AllDirs); }
144   else{ dirtreeModel->setFilter(QDir::NoDotAndDotDot | QDir::AllDirs); }
145 }
146 
showThumbnails(bool show)147 void DirWidget::showThumbnails(bool show){
148   BW->showThumbnails(show);
149   if(RCBW!=0){ RCBW->showThumbnails(show); }
150 }
151 
setThumbnailSize(int px)152 void DirWidget::setThumbnailSize(int px){
153   BW->setThumbnailSize(px);
154   if(RCBW!=0){ RCBW->setThumbnailSize(px); }
155   ui->tool_zoom_in->setEnabled(px < 256); //upper limit on image sizes
156   ui->tool_zoom_out->setEnabled(px >16); //lower limit on image sizes
157 }
158 
setTreeSortMode(int num)159 void DirWidget::setTreeSortMode(int num){
160   bool now = true;
161   BW->setTreeWidgetSortColumn(num, now);
162   if(RCBW!=0){ RCBW->setTreeWidgetSortColumn(num, now ); }
163 }
164 
165 //====================
166 //         Folder Pane
167 //====================
adjustTreeWidget(float percent)168 void DirWidget::adjustTreeWidget(float percent){
169   ui->splitter->setSizes( QList<int>() << this->width()*(percent/100.0) << this->width() * ((100.0-percent)/100.0) );
170 }
171 
on_folderViewPane_clicked(const QModelIndex & index)172 void DirWidget::on_folderViewPane_clicked(const QModelIndex &index){
173   QString tPath = dirtreeModel->fileInfo(index).absoluteFilePath();     // get what was clicked
174   ChangeDir(tPath);
175 }
176 
177 // ================
178 //    PUBLIC SLOTS
179 // ================
180 
LoadSnaps(QString basedir,QStringList snaps)181 void DirWidget::LoadSnaps(QString basedir, QStringList snaps){
182   //Save these value internally for use later
183   //qDebug() << "ZFS Snapshots available:" << basedir << snaps;
184   snapbasedir = basedir;
185   snapshots = snaps;
186   //if(!snapbasedir.isEmpty()){ watcher->addPath(snapbasedir); } //add this to the watcher in case snapshots get created/removed
187   //Now update the UI as necessary
188   if(ui->tool_snap->menu()==0){
189     ui->tool_snap->setMenu(new QMenu(this));
190     connect(ui->tool_snap->menu(), SIGNAL(triggered(QAction*)), this, SLOT(direct_snap_selected(QAction*)) );
191   }
192   ui->tool_snap->menu()->clear();
193   for(int i=0; i<snapshots.length(); i++){
194     QAction *tmp = ui->tool_snap->menu()->addAction(snapshots[i]);
195       tmp->setWhatsThis(snapshots[i]);
196   }
197   ui->slider_snap->setRange(0, snaps.length());
198   if(currentBrowser()->currentDirectory().contains(ZSNAPDIR)){
199   //The user was already within a snapshot - figure out which one and set the slider appropriately
200    int index = snaps.indexOf( currentBrowser()->currentDirectory().section(ZSNAPDIR,1,1).section("/",0,0) );
201    if(index < 0){ index = snaps.length(); } //unknown - load the system (should never happen)
202    ui->slider_snap->setValue(index);
203   }else{
204    ui->slider_snap->setValue(snaps.length()); //last item (normal system)
205   }
206   on_slider_snap_valueChanged();
207   QApplication::processEvents(); //let the slider changed signal get thrown away before we re-enable the widget
208   ui->group_snaps->setEnabled(!snaps.isEmpty());
209   ui->group_snaps->setVisible(!snaps.isEmpty());
210   ui->tool_snap_newer->setEnabled(ui->slider_snap->value() < ui->slider_snap->maximum());
211   ui->tool_snap_older->setEnabled(ui->slider_snap->value() > ui->slider_snap->minimum());
212 }
213 
refresh()214 void DirWidget::refresh(){
215   currentBrowser()->changeDirectory(""); //refresh current dir
216 }
217 
218 //Theme change functions
UpdateIcons()219 void DirWidget::UpdateIcons(){
220   //Snapshot buttons
221   ui->tool_snap_newer->setIcon(LXDG::findIcon("go-next-view","") );
222   ui->tool_snap_older->setIcon(LXDG::findIcon("go-previous-view","") );
223 
224   //ToolBar Buttons
225   ui->actionBack->setIcon( LXDG::findIcon("go-previous","") );
226   ui->actionUp->setIcon( LXDG::findIcon("go-up","") );
227   ui->actionHome->setIcon( LXDG::findIcon("go-home","") );
228   ui->actionMenu->setIcon( LXDG::findIcon("open-menu", "view-more-vertical") );
229   ui->actionSingleColumn->setIcon(LXDG::findIcon("view-right-close","view-close") );
230   ui->actionDualColumn->setIcon(LXDG::findIcon("view-right-new","view-split-left-right") );
231 
232   ui->tool_zoom_in->setIcon(LXDG::findIcon("zoom-in",""));
233   ui->tool_zoom_out->setIcon(LXDG::findIcon("zoom-out",""));
234 }
235 
UpdateText()236 void DirWidget::UpdateText(){
237   ui->retranslateUi(this);
238   BW->retranslate();
239   if(RCBW!=0){ RCBW->retranslate(); }
240 }
241 
242 // =================
243 //       PRIVATE
244 // =================
createShortcuts()245 void DirWidget::createShortcuts(){
246   kZoomIn= new QShortcut(QKeySequence(QKeySequence::ZoomIn),this);
247   kZoomOut= new QShortcut(QKeySequence(QKeySequence::ZoomOut),this);
248   kNewFile= new QShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_F),this);
249   kNewDir= new QShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_N), this);
250   kNewXDG= new QShortcut(QKeySequence(Qt::CTRL+Qt::Key_G),this);
251   kCut= new QShortcut(QKeySequence(QKeySequence::Cut),this);
252   kCopy= new QShortcut(QKeySequence(QKeySequence::Copy),this);
253   kPaste= new QShortcut(QKeySequence(QKeySequence::Paste),this);
254   kRename= new QShortcut(QKeySequence(Qt::Key_F2),this);
255   kExtract= new QShortcut(QKeySequence(Qt::CTRL+Qt::Key_E), this);
256   kArchive= new QShortcut(QKeySequence(Qt::CTRL+Qt::Key_R), this);
257   kFav= new QShortcut(QKeySequence(Qt::Key_F3),this);
258   kDel= new QShortcut(QKeySequence(QKeySequence::Delete),this);
259   kOpSS= new QShortcut(QKeySequence(Qt::Key_F6),this);
260   kOpMM= new QShortcut(QKeySequence(Qt::Key_F7),this);
261   kOpTerm = new QShortcut(QKeySequence(Qt::Key_F1),this);
262 
263   connect(kZoomIn, SIGNAL(activated()), this, SLOT(on_tool_zoom_in_clicked()) );
264   connect(kZoomOut, SIGNAL(activated()), this, SLOT(on_tool_zoom_out_clicked()) );
265   connect(kNewFile, SIGNAL(activated()), this, SLOT(createNewFile()) );
266   connect(kNewDir, SIGNAL(activated()), this, SLOT(createNewDir()) );
267   connect(kNewXDG, SIGNAL(activated()), this, SLOT(createNewXDGEntry()) );
268   connect(kCut, SIGNAL(activated()), this, SLOT(cutFiles()) );
269   connect(kCopy, SIGNAL(activated()), this, SLOT(copyFiles()) );
270   connect(kPaste, SIGNAL(activated()), this, SLOT(pasteFiles()) );
271   connect(kRename, SIGNAL(activated()), this, SLOT(renameFiles()) );
272   connect(kExtract, SIGNAL(activated()), this, SLOT(autoExtractFiles()) );
273   //connect(kArchive, SIGNAL(activated()), this, SLOT(autoArchiveFiles()) );
274   connect(kFav, SIGNAL(activated()), this, SLOT(favoriteFiles()) );
275   connect(kDel, SIGNAL(activated()), this, SLOT(removeFiles()) );
276   connect(kOpSS, SIGNAL(activated()), this, SLOT(openInSlideshow()) );
277   connect(kOpMM, SIGNAL(activated()), this, SLOT(openMultimedia()) );
278   connect(kOpTerm, SIGNAL(activated()), this, SLOT(openTerminal()) );
279 }
280 
createMenus()281 void DirWidget::createMenus(){
282   //Note: contextMenu already created - this is just for the sub-items
283   if(cNewMenu==0){ cNewMenu = new QMenu(this); }
284   else{ cNewMenu->clear(); }
285   cNewMenu->setTitle(tr("Create...") );
286   cNewMenu->setIcon( LXDG::findIcon("list-add","") );
287   cNewMenu->addAction(LXDG::findIcon("document-new",""), tr("File"), this, SLOT(createNewFile()), kNewFile->key() );
288   cNewMenu->addAction(LXDG::findIcon("folder-new",""), tr("Directory"), this, SLOT(createNewDir()), kNewDir->key() );
289   if(LUtils::isValidBinary("lumina-fileinfo")){ cNewMenu->addAction(LXDG::findIcon("system-run",""), tr("Application Launcher"), this, SLOT(createNewXDGEntry()), kNewXDG->key() ); }
290 
291   if(cOpenMenu==0){ cOpenMenu = new QMenu(this); }
292   else{ cOpenMenu->clear(); }
293   cOpenMenu->setTitle(tr("Launch..."));
294   cOpenMenu->setIcon( LXDG::findIcon("quickopen","") );
295   cOpenMenu->addAction(LXDG::findIcon("utilities-terminal",""), tr("Open Current Dir in a Terminal"), this, SLOT(openTerminal()), kOpTerm->key());
296   cOpenMenu->addAction(LXDG::findIcon("media-slideshow","view-presentation"), tr("SlideShow"), this, SLOT(openInSlideshow()), kOpSS->key());
297   cOpenMenu->addAction(LXDG::findIcon("media-playback-start-circled","media-playback-start"), tr("Multimedia Player"), this, SLOT(openMultimedia()), kOpMM->key());
298   if(LUtils::isValidBinary("qsudo")){
299     cOpenMenu->addAction(LXDG::findIcon("system-file-manager-root", ""), tr("Open Current Dir as Root"), this, SLOT(openRootFM()));
300   }
301 
302   if(cArchiveMenu==0){ cArchiveMenu = new QMenu(this); }
303   cArchiveMenu->setTitle(tr("Archive Options"));
304   cArchiveMenu->setIcon( LXDG::findIcon("utilities-file-archiver","application-x-compressed-tar") );
305 
306   /*
307   if(cFModMenu==0){ cFModMenu = new QMenu(this); }
308   else{ cFModMenu->clear(); }
309   cFModMenu->setTitle(tr("Modify Files..."));
310   cFModMenu->setIcon( LXDG::findIcon("document-edit","") );
311   cFModMenu->addAction(LXDG::findIcon("edit-cut",""), tr("Cut Selection"), this, SLOT(cutFiles()), kCut->key() );
312   cFModMenu->addAction(LXDG::findIcon("edit-copy",""), tr("Copy Selection"), this, SLOT(copyFiles()), kCopy->key() );
313   cFModMenu->addSeparator();
314   cFModMenu->addAction(LXDG::findIcon("edit-rename",""), tr("Rename..."), this, SLOT(renameFiles()), kRename->key() );
315   cFModMenu->addSeparator();
316   cFModMenu->addAction(LXDG::findIcon("edit-delete",""), tr("Delete Selection"), this, SLOT(removeFiles()), kDel->key() );
317 */
318 
319 //---------------------------------------------------//
320 
321   if(cOpenWithMenu==0){
322     cOpenWithMenu = new QMenu(this);
323     connect(cOpenWithMenu, SIGNAL(triggered(QAction*)), this, SLOT(OpenWithApp(QAction*)) );
324   }
325   else{ cOpenWithMenu->clear(); }
326   cOpenWithMenu->setTitle(tr("Open with..."));
327   cOpenWithMenu->setIcon( LXDG::findIcon("system-run-with","") );
328 
329 //---------------------------------------------------//
330   if(cFViewMenu==0){ cFViewMenu = new QMenu(this); }
331   else{ cFViewMenu->clear(); }
332   cFViewMenu->setTitle(tr("View Files..."));
333   cFViewMenu->setIcon( LXDG::findIcon("document-preview","") );
334   cFViewMenu->addAction(LXDG::findIcon("document-encrypted",""), tr("Checksums"), this, SLOT(fileCheckSums()) );
335   if(LUtils::isValidBinary("lumina-fileinfo")){
336     cFViewMenu->addAction(LXDG::findIcon("edit-find-replace",""), tr("Properties"), this, SLOT(fileProperties()) );
337   }
338 }
339 
currentBrowser()340 BrowserWidget* DirWidget::currentBrowser(){
341   if(cBID.isEmpty() || RCBW==0){ return BW; }
342   else{ return RCBW; }
343 }
344 
currentDirFiles()345 QStringList DirWidget::currentDirFiles(){
346   return currentBrowser()->currentItems(-1);  //files only
347 }
348 // =================
349 //    PRIVATE SLOTS
350 // =================
splitterMoved()351 void DirWidget::splitterMoved(){
352   float percent = (ui->splitter->sizes().first() / ( (float) this->width()) )*100.0;
353   this->emit treeWidgetSizeChanged(percent);
354 }
355 
356 //UI BUTTONS
on_tool_zoom_in_clicked()357 void DirWidget::on_tool_zoom_in_clicked(){
358   int size = BW->thumbnailSize();
359   size += 16;
360   setThumbnailSize(size);
361   //Now Save the size value as the default for next time
362   settings->setValue("iconsize", size);
363 }
364 
on_tool_zoom_out_clicked()365 void DirWidget::on_tool_zoom_out_clicked(){
366   int size = BW->thumbnailSize();
367   if(size <= 16){ return; }
368   size -= 16;
369   setThumbnailSize(size);
370   //Now Save the size value as the default for next time
371   settings->setValue("iconsize", size);
372 }
373 
374 // -- Top Snapshot Buttons
on_tool_snap_newer_clicked()375 void DirWidget::on_tool_snap_newer_clicked(){
376   ui->slider_snap->setValue( ui->slider_snap->value()+1 );
377 }
378 
on_tool_snap_older_clicked()379 void DirWidget::on_tool_snap_older_clicked(){
380   ui->slider_snap->setValue( ui->slider_snap->value()-1 );
381 }
382 
on_slider_snap_valueChanged(int val)383 void DirWidget::on_slider_snap_valueChanged(int val){
384   bool labelsonly = false;
385   if(val==-1){ val = ui->slider_snap->value(); labelsonly=true; }
386   //Update the snapshot interface
387   ui->tool_snap_newer->setEnabled(val < ui->slider_snap->maximum());
388   ui->tool_snap_older->setEnabled(val > ui->slider_snap->minimum());
389   if(val >= snapshots.length() || val < 0){
390     ui->tool_snap->setText(tr("Current"));
391   }else if(QFile::exists(snapbasedir+snapshots[val])){
392     ui->tool_snap->setText( QFileInfo(snapbasedir+snapshots[val]).lastModified().toString(Qt::DefaultLocaleShortDate) );
393   }
394   //Exit if a non-interactive snapshot change
395   if(!ui->group_snaps->isEnabled() || labelsonly){ return; } //internal change - do not try to change the actual info
396   //Determine which snapshot is now selected
397   QString dir;
398   if(DEBUG){ qDebug() << "Changing snapshot:" << currentBrowser()->currentDirectory() << val << snapbasedir; }
399   //stopload = true; //stop any currently-loading procedures
400   if(val >= snapshots.length() || val < 0){ //active system selected
401     if(DEBUG){ qDebug() << " - Load Active system:" << normalbasedir; }
402     dir = normalbasedir;
403   }else{
404     dir = snapbasedir+snapshots[val]+"/";
405     if(!QFile::exists(dir)){
406       //This snapshot must have been removed in the background by pruning tools
407       //    move to a newer snapshot or the current base dir as necessary
408       qDebug() << "Snapshot no longer available:" << dir;
409       qDebug() << " - Reloading available snapshots";
410       emit findSnaps(ID, normalbasedir);
411       return;
412     }
413     //Need to figure out the relative path within the snapshot
414       snaprelpath = normalbasedir.section(snapbasedir.section(ZSNAPDIR,0,0), 1,1000);
415       if(DEBUG){ qDebug() << " - new snapshot-relative path:" << snaprelpath; }
416     dir.append(snaprelpath);
417     dir.replace("//","/"); //just in case any duplicate slashes from all the split/combining
418     if(DEBUG){ qDebug() << " - Load Snapshot:" << dir; }
419   }
420   //Make sure this directory exists, and back up as necessary
421   if(dir.isEmpty()){ return; }
422   //Load the newly selected snapshot
423   currentBrowser()->changeDirectory(dir);
424 }
425 
direct_snap_selected(QAction * act)426 void DirWidget::direct_snap_selected(QAction *act){
427   QString snap = act->whatsThis();
428   int val = snapshots.indexOf(snap);
429   if(val<0){ return; }
430   else{ ui->slider_snap->setValue(val); }
431 }
432 
433 //Top Toolbar buttons
on_actionBack_triggered()434 void DirWidget::on_actionBack_triggered(){
435   QStringList history = currentBrowser()->history();
436   if(history.length()<2){ return; } //cannot do anything
437   QString dir = history.takeLast();
438   //qDebug() << "Go Back:" << dir << normalbasedir << history;
439   if(dir == normalbasedir){
440     dir = history.takeLast();
441   }
442   history << dir; //make sure the current dir is always last in the history
443   currentBrowser()->changeDirectory(dir);
444   currentBrowser()->setHistory(history); //re-write the history to account for going backwards
445   ui->actionBack->setEnabled(history.length()>1);
446 }
447 
on_actionUp_triggered()448 void DirWidget::on_actionUp_triggered(){
449 QString dir = currentBrowser()->currentDirectory().section("/",0,-2);
450   if(dir.isEmpty())
451    dir = "/";
452    //Quick check to ensure the directory exists
453    while(!QFile::exists(dir) && !dir.isEmpty()){
454    dir = dir.section("/",0,-2); //back up one additional dir
455   }
456   currentBrowser()->changeDirectory(dir);
457 }
458 
on_actionHome_triggered()459 void DirWidget::on_actionHome_triggered(){
460   currentBrowser()->changeDirectory(QDir::homePath());
461 }
462 
dir_changed()463 void DirWidget::dir_changed(){
464   QString dir = line_dir->text().simplified();
465   //Run the dir through the user-input checks
466   dir = LUtils::PathToAbsolute(dir);
467   //qDebug() << "Dir Changed:" << dir;
468   //Quick check to ensure the directory exists
469   while(!QFile::exists(dir) && !dir.isEmpty()){
470     dir = dir.section("/",0,-2); //back up one additional dir
471   }
472   //qDebug() << " - Now Dir:" << dir;
473   currentBrowser()->changeDirectory(dir);
474   //emit LoadDirectory(ID, dir);
475 }
476 
477 
on_actionSingleColumn_triggered(bool checked)478 void DirWidget::on_actionSingleColumn_triggered(bool checked){
479   if(!checked){ return; }
480   if(RCBW==0){ return; } //nothing to do
481   ui->browser_layout->removeWidget(RCBW);
482   RCBW->stop();
483   BrowserWidget *tmp = RCBW;
484   RCBW = 0;
485   setCurrentBrowser(""); //reset back to the remaining browser
486   QTimer::singleShot(10000, tmp, SLOT(deleteLater()));
487 }
488 
on_actionDualColumn_triggered(bool checked)489 void DirWidget::on_actionDualColumn_triggered(bool checked){
490   if(!checked){ return; }
491   if(RCBW!=0){ return; } //nothing to do
492   RCBW = new BrowserWidget("rc", this);
493   ui->browser_layout->addWidget(RCBW);
494   connect(RCBW, SIGNAL(dirChange(QString, bool)), this, SLOT(currentDirectoryChanged(QString)) );
495   connect(RCBW, SIGNAL(itemsActivated()), this, SLOT(runFiles()) );
496   connect(RCBW, SIGNAL(DataDropped(QString, QStringList)), this, SIGNAL(PasteFiles(QString, QStringList)) );
497   connect(RCBW, SIGNAL(contextMenuRequested()), this, SLOT(OpenContextMenu()) );
498   connect(RCBW, SIGNAL(updateDirectoryStatus(QString)), this, SLOT(dirStatusChanged(QString)) );
499   connect(RCBW, SIGNAL(hasFocus(QString)), this, SLOT(setCurrentBrowser(QString)) );
500   //Now make sure it has all the same settings as the main browser
501   setCurrentBrowser("rc");
502   RCBW->showDetails(BW->hasDetails());
503   RCBW->showHiddenFiles( BW->hasHiddenFiles());
504   RCBW->setThumbnailSize( BW->thumbnailSize());
505   RCBW->showThumbnails( BW->hasThumbnails());
506   RCBW->changeDirectory( BW->currentDirectory());
507 }
508 
on_actionMenu_triggered()509 void DirWidget::on_actionMenu_triggered(){
510   OpenContextMenu();
511 }
512 
513 
514 // - Other Actions without a specific button on the side
fileCheckSums()515 void DirWidget::fileCheckSums(){
516   QStringList files = currentBrowser()->currentSelection();
517   if(files.isEmpty()){ return; }
518   qDebug() << "Run Checksums:" << files;
519   QStringList info = LOS::Checksums(files);
520   qDebug() << " - Info:" << info;
521   if(info.isEmpty() || (info.length() != files.length()) ){ return; }
522   for(int i=0; i<info.length(); i++){
523     info[i] = QString("%2\n\t(%1)").arg(files[i].section("/",-1), info[i]);
524   }
525   ScrollDialog dlg(this);
526     dlg.setWindowTitle( tr("File Checksums:") );
527     dlg.setWindowIcon( LXDG::findIcon("document-encrypted","") );
528     dlg.setText(info.join("\n"));
529   dlg.exec();
530 }
531 
fileProperties()532 void DirWidget::fileProperties(){
533   QStringList sel = currentBrowser()->currentSelection();
534   //qDebug() << "Open File properties:" << sel;
535   if(sel.isEmpty()){ return; }
536   if(!LUtils::isValidBinary("lumina-fileinfo")){
537     //It should never get to this point due to checks earlier - but just in case...
538     QMessageBox::warning(this, tr("Missing Utility"), tr("The \"lumina-fileinfo\" utility could not be found on the system. Please install it first.") );
539     return;
540   }
541   for(int i=0; i<sel.length(); i++){
542     QProcess::startDetached("lumina-fileinfo \""+sel[i]+"\""); //use absolute paths
543   }
544 }
545 
openTerminal()546 void DirWidget::openTerminal(){
547   emit LaunchTerminal(currentBrowser()->currentDirectory());
548 }
549 
550 //Browser Functions
OpenContextMenu()551 void DirWidget::OpenContextMenu(){
552   //Now open the menu at the current cursor location
553   contextMenu->popup(QCursor::pos());
554 }
555 
UpdateContextMenu()556 void DirWidget::UpdateContextMenu(){
557   //First generate the context menu based on the selection
558   QStringList sel = currentBrowser()->currentSelection();
559   //qDebug() << "Update context menu";
560   //qDebug() << "  Selection:" << sel;
561   contextMenu->clear();
562 
563   if(!sel.isEmpty()){
564    contextMenu->addAction(LXDG::findIcon("system-run",""), tr("Open"), this, SLOT(runFiles()) );
565    //contextMenu->addAction(LXDG::findIcon("system-run-with",""), tr("Open With..."), this, SLOT(runWithFiles()) );
566    contextMenu->addMenu(cOpenWithMenu);
567    bool ok = (QString(getenv("XDG_CURRENT_DESKTOP"))=="Lumina");
568    if(ok){
569      static QStringList imageformats = LUtils::imageExtensions();
570      for(int i=0; i<sel.length() && ok; i++){
571         ok = imageformats.contains(sel[i].section(".",-1));
572       }
573       contextMenu->addAction(LXDG::findIcon("preferences-desktop-wallpaper","preferences-desktop"), tr("Set as Wallpaper"), this, SLOT(setAsWallpaper()) )->setEnabled(ok);
574     }
575   }
576   contextMenu->addSection(LXDG::findIcon("unknown",""), tr("File Operations"));
577  // contextMenu->addMenu(cFModMenu);
578  //   cFModMenu->setEnabled(!sel.isEmpty() && canmodify);
579 
580   if(!sel.isEmpty()){
581       contextMenu->addAction(LXDG::findIcon("edit-rename",""), tr("Rename..."), this, SLOT(renameFiles()), kRename->key() )->setEnabled(canmodify);
582       contextMenu->addAction(LXDG::findIcon("edit-cut",""), tr("Cut Selection"), this, SLOT(cutFiles()), kCut->key() )->setEnabled(canmodify);
583       contextMenu->addAction(LXDG::findIcon("edit-copy",""), tr("Copy Selection"), this, SLOT(copyFiles()), kCopy->key() )->setEnabled(canmodify);
584   }
585     if( QApplication::clipboard()->mimeData()->hasFormat("x-special/lumina-copied-files") ){
586       contextMenu->addAction(LXDG::findIcon("edit-paste",""), tr("Paste"), this, SLOT(pasteFiles()), QKeySequence(Qt::CTRL+Qt::Key_V) )->setEnabled(canmodify);
587     }
588     if(!sel.isEmpty()){
589       contextMenu->addSeparator();
590       contextMenu->addAction(LXDG::findIcon("edit-delete",""), tr("Delete Selection"), this, SLOT(removeFiles()), kDel->key() )->setEnabled(canmodify);
591       contextMenu->addSeparator();
592     }
593     if(LUtils::isValidBinary("lumina-archiver") && sel.length() >=1 && canmodify){
594       cArchiveMenu->clear();
595       if(sel.length()==1 && ( XDGMime::fromFileName(sel[0]).endsWith("-tar") ||
596                                                 XDGMime::fromFileName(sel[0]).endsWith("-image") ||
597                                                  ( XDGMime::fromFileName(sel[0]).contains("zip") &&
598                                                    !XDGMime::fromFileName(sel[0]).endsWith("epub+zip") &&
599                                                    !XDGMime::fromFileName(sel[0]).endsWith("vnd.comicbook+zip" )
600                                                   )
601                                                  )
602                                                ){
603         cArchiveMenu->addAction(LXDG::findIcon("archive",""), tr("Extract Here"), this, SLOT(autoExtractFiles()), kExtract->key() );
604       }else{
605         cArchiveMenu->addAction(LXDG::findIcon("archive",""), tr("Archive Selection"), this, SLOT(autoArchiveFiles()), kArchive->key() );
606       }
607       contextMenu->addMenu(cArchiveMenu);
608     }
609     contextMenu->addMenu(cFViewMenu);
610     cFViewMenu->setEnabled(!sel.isEmpty());
611 
612     //Now add the general selection options
613    contextMenu->addSection(LXDG::findIcon("folder","inode/directory"), tr("Directory Operations"));
614    if(canmodify){
615      contextMenu->addMenu(cNewMenu);
616    }
617    contextMenu->addMenu(cOpenMenu);
618   //=====================
619   //PREFAPPS = getPreferredApplications();
620   //qDebug() << "Preferred Apps:" << PREFAPPS;
621   cOpenWithMenu->clear();
622   //Now get the application mimetype for the file extension (if available)
623   QStringList mimetypes;
624   for(int i=0; i<sel.length(); i++){
625     QStringList mimes = LXDG::findAppMimeForFile(sel[i], true).split("::::"); //use all mimetypes
626     if(mimetypes.isEmpty()){ mimetypes << mimes; }
627     else{
628       //need to verify that the mimetypes are the same before adding them.
629       QStringList test; test << mimetypes << mimes;
630       if(test.removeDuplicates()>0){ mimetypes = test; }
631       else{
632         //Bad match - incompatible file types are selected - disable the recommendations
633         mimetypes.clear();
634         break;
635       }
636     }
637   }
638   //Now add all the detected applications
639   if(!mimetypes.isEmpty()){
640     static XDGDesktopList applist;
641     applist.updateList();
642     QList<XDGDesktop*> apps = applist.apps(false,true);
643     QList<XDGDesktop*> found;
644     for(int a=0; a<apps.length(); a++){
645       if(apps[a]->mimeList.isEmpty()){ continue; } //no corresponding mime types for this app
646         QStringList test; test << mimetypes << apps[a]->mimeList;
647        if(test.removeDuplicates()>0){ found << apps[a]; }
648     }
649     if(!found.isEmpty()){
650       //sort the apps by name
651       found = LXDG::sortDesktopNames(found);
652       //add apps to the menu
653       for(int i=0; i<found.length(); i++){
654         QAction *act = cOpenWithMenu->addAction( LXDG::findIcon(found[i]->icon, ""), found[i]->name );
655           act->setToolTip(found[i]->comment);
656           act->setWhatsThis(found[i]->filePath);
657       }
658       cOpenWithMenu->addSeparator();
659     }
660   }
661   cOpenWithMenu->addAction(LXDG::findIcon("system-run-with",""), tr("Other..."), this, SLOT(runWithFiles()) );
662 }
663 
currentDirectoryChanged(QString cur,bool widgetonly)664 void DirWidget::currentDirectoryChanged(QString cur, bool widgetonly){
665   //qDebug() << "Got current directory changed:" << cur << widgetonly;
666   if (cur.isEmpty()) { return; }
667   QFileInfo info(cur);
668   canmodify = info.isWritable();
669   if(widgetonly){ ui->label_status->setText(currentBrowser()->status()); }
670   else if( !currentBrowser()->isEnabled() ){ ui->label_status->setText(tr("Loading...")); }
671   //qDebug() << "Start search for snapshots";
672   if(!cur.contains("/.zfs/snapshot") ){
673     normalbasedir = cur;
674     ui->group_snaps->setVisible(false);
675     emit findSnaps(ID, cur);
676     //qDebug() << "Changed to directory:" << cur;
677   }else{
678     //Re-assemble the normalbasedir variable (in case moving around within a snapshot)
679     normalbasedir = cur;
680     normalbasedir.replace( QRegExp("\\/\\.zfs\\/snapshot/([^/]+)\\/"), "/" );
681     //qDebug() << "Changed to snapshot:" << cur << normalbasedir;
682   }
683   ui->actionBack->setEnabled( currentBrowser()->history().length()>1 );
684   line_dir->setText(normalbasedir);
685   emit TabNameChanged(ID, normalbasedir.section("/",-1));
686   QModelIndex index = dirtreeModel->index(normalbasedir,0);
687   ui->folderViewPane->setCurrentIndex( index );
688   ui->folderViewPane->scrollTo(index, QAbstractItemView::PositionAtCenter);
689 }
690 
currentDirectoryChanged(bool widgetonly)691 void DirWidget::currentDirectoryChanged(bool widgetonly){
692   //overload for the more expansive slot above
693   QString cur = currentBrowser()->currentDirectory();
694   currentDirectoryChanged(cur, widgetonly);
695 }
696 
dirStatusChanged(QString stat)697 void DirWidget::dirStatusChanged(QString stat){
698   if(!canmodify){ stat.prepend(tr("(Limited Access) ")); }
699   ui->label_status->setText(stat);
700 }
701 
setCurrentBrowser(QString id)702 void DirWidget::setCurrentBrowser(QString id){
703   //qDebug() << "Set Current Browser:" << id;
704   if(id==cBID){ return; } //no change
705   cBID = id;
706   currentDirectoryChanged(true); //update all the averarching widget elements (widget only)
707   //Now adjust the frame/highlighting around the "active" browser
708   if(RCBW==0){ BW->setShowActive(true); }
709   else{
710     BW->setShowActive( cBID.isEmpty() );
711     RCBW->setShowActive( !cBID.isEmpty() );
712   }
713 }
714 
715 //Context Menu Functions
createNewFile()716 void DirWidget::createNewFile(){
717   if(!canmodify){ return; } //cannot create anything here
718   //Prompt for the new filename
719   bool ok = false;
720   QString newdocument = QInputDialog::getText(this, tr("New Document"), tr("Name:"), QLineEdit::Normal, "", \
721         &ok, 0, Qt::ImhFormattedNumbersOnly | Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly);
722   if(!ok || newdocument.isEmpty()){ return; }
723   //Create the empty file
724   QString full = currentBrowser()->currentDirectory();
725   if(!full.endsWith("/")){ full.append("/"); }
726   //verify the new file does not already exist
727   if(QFile::exists(full+newdocument)){
728     QMessageBox::warning(this, tr("Invalid Name"), tr("A file or directory with that name already exists! Please pick a different name."));
729     QTimer::singleShot(0,this, SLOT(createNewFile()) ); //repeat this function
730     return;
731   }
732   QFile file(full+newdocument);
733   if(file.open(QIODevice::ReadWrite)){
734     //If successfully opened, it has created a blank file
735     file.close();
736   }else{
737     QMessageBox::warning(this, tr("Error Creating Document"), tr("The document could not be created. Please ensure that you have the proper permissions."));
738   }
739 }
740 
741 
createNewFolder(QWidget * parent,BrowserWidget * browser)742 void DirWidget::createNewFolder(QWidget *parent, BrowserWidget *browser){
743 
744     //Prompt for the new dir name
745     bool ok = false;
746     QString newdir = QInputDialog::getText(parent, tr("New Directory"), tr("Name:"), QLineEdit::Normal, "", \
747           &ok, 0, Qt::ImhFormattedNumbersOnly | Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly);
748     if(!ok || newdir.isEmpty()){ return; }
749     //Now create the new dir
750     QString full = browser->currentDirectory();
751     if(!full.endsWith("/")){ full.append("/"); }
752     QDir dir(full); //open the current dir
753     full.append(newdir); //append the new name to the current dir
754     //Verify that the new dir does not already exist
755     if(dir.exists(full)){
756       QMessageBox::warning(parent, tr("Invalid Name"), tr("A file or directory with that name already exists! Please pick a different name."));
757       QTimer::singleShot(0,parent, SLOT(createNewDir(parent, browser)) ); //repeat this function
758     }else{
759       if(!dir.mkdir(newdir) ){
760         QMessageBox::warning(parent, tr("Error Creating Directory"), tr("The directory could not be created. Please ensure that you have the proper permissions to modify the current directory."));
761       }
762     }
763 }
764 
createNewDir()765 void DirWidget::createNewDir(){
766   if(!canmodify){ return; } //cannot create anything here
767   createNewFolder(this, currentBrowser());
768 }
769 
createNewXDGEntry()770 void DirWidget::createNewXDGEntry(){
771   if(!canmodify){ return; } //cannot create anything here
772   //Prompt for the new filename
773   bool ok = false;
774   QString newdocument = QInputDialog::getText(this, tr("New Document"), tr("Name:"), QLineEdit::Normal, "", \
775         &ok, 0, Qt::ImhFormattedNumbersOnly | Qt::ImhUppercaseOnly | Qt::ImhLowercaseOnly);
776   if(!ok || newdocument.isEmpty()){ return; }
777   if(!newdocument.endsWith(".desktop")){ newdocument.append(".desktop"); }
778   //Create the empty file
779   QString full = currentBrowser()->currentDirectory();
780   if(!full.endsWith("/")){ full.append("/"); }
781   //Verify the file does not already exist
782   if(QFile::exists(full+newdocument)){
783     QMessageBox::warning(this, tr("Invalid Name"), tr("A file or directory with that name already exists! Please pick a different name."));
784     QTimer::singleShot(0,this, SLOT(createNewFile()) ); //repeat this function
785     return;
786   }
787   QProcess::startDetached("lumina-fileinfo -application \""+full+newdocument+"\"");
788 }
789 
790 /*void DirWidget::createNewSymlink{
791 
792  }*/
793 
794 // - Selected FILE operations
795 
796 
cutFiles()797 void DirWidget::cutFiles(){
798   QStringList sel = currentBrowser()->currentSelection();
799   if(sel.isEmpty() || !canmodify){ return; }
800   emit CutFiles(sel);
801 }
802 
copyFiles()803 void DirWidget::copyFiles(){
804   QStringList sel = currentBrowser()->currentSelection();
805   if(sel.isEmpty()){ return; }
806   emit CopyFiles(sel);
807 }
808 
pasteFiles()809 void DirWidget::pasteFiles(){
810   if( !canmodify ){ return; }
811   emit PasteFiles(currentBrowser()->currentDirectory(), QStringList() );
812 }
813 
renameFiles()814 void DirWidget::renameFiles(){
815   QStringList sel = currentBrowser()->currentSelection();
816   if(sel.isEmpty() || !canmodify){ return; }
817   qDebug() << "Deleting selected Items:" << sel;
818   emit RenameFiles(sel);
819 }
820 
favoriteFiles()821 void DirWidget::favoriteFiles(){
822   QStringList sel = currentBrowser()->currentSelection();
823   if(sel.isEmpty()){ return; }
824   emit FavoriteFiles(sel);
825 }
826 
removeFiles()827 void DirWidget::removeFiles(){
828   QStringList sel = currentBrowser()->currentSelection();
829   if(sel.isEmpty() || !canmodify){ return; }
830   qDebug() << "Deleting selected Items:" << sel;
831   emit RemoveFiles(sel);
832 }
833 
runFiles()834 void DirWidget::runFiles(){
835   //qDebug() << "Run Files";
836   QStringList sel = currentBrowser()->currentSelection();
837   if(sel.isEmpty()){ return; }
838   QStringList dirs;
839   for(int i=0; i<sel.length(); i++){
840     if(QFileInfo(sel[i]).isDir()){
841       dirs << sel[i];
842     }else{
843       QProcess::startDetached("lumina-open \""+sel[i]+"\"");
844     }
845   }
846   //qDebug() << " - got Dirs:" << dirs;
847   if(!dirs.isEmpty()){
848     currentBrowser()->changeDirectory( dirs.takeFirst()); //load the first directory in this widget
849   }
850   if(!dirs.isEmpty()){
851     emit OpenDirectories(dirs); //open the rest of the directories in other tabs
852   }
853 }
854 
runWithFiles()855 void DirWidget::runWithFiles(){
856   QStringList sel = currentBrowser()->currentSelection();
857   if(sel.isEmpty()){ return; }
858   QStringList dirs;
859   for(int i=0; i<sel.length(); i++){
860     if(QFileInfo(sel[i]).isDir()){
861       dirs << sel[i];
862     }else{
863       QProcess::startDetached("lumina-open -select \""+sel[i]+"\"");
864     }
865   }
866   if(!dirs.isEmpty()){
867     emit OpenDirectories(dirs); //open the rest of the directories in other tabs
868   }
869 }
870 
871 /*void DirWidget::attachToNewEmail(){
872 
873 }*/
874 
875 // - Context-specific operations
openInSlideshow()876 void DirWidget::openInSlideshow(){
877   QStringList sel = currentBrowser()->currentSelection();
878   if(sel.isEmpty()){ sel = currentDirFiles(); }
879   //Now turn them all into a list and emit them
880     LFileInfoList list;
881     for(int i=0; i<sel.length(); i++){
882       if(sel.endsWith(".desktop")){ continue; } //simplification to make sure we don't read any files which are not needed
883       LFileInfo info(sel[i]);
884       if( info.isImage() ){  list << info; } //add to the list
885     }
886   if(!list.isEmpty()){ emit ViewFiles(list); }
887 }
888 
openMultimedia()889 void DirWidget::openMultimedia(){
890   QStringList sel = currentBrowser()->currentSelection();
891   if(sel.isEmpty()){ sel = currentDirFiles(); }
892   //Now turn them all into a list and emit them
893     LFileInfoList list;
894     for(int i=0; i<sel.length(); i++){
895       if(sel.endsWith(".desktop")){ continue; } //simplification to make sure we don't read any files which are not needed
896       LFileInfo info(sel[i]);
897       if( info.isAVFile() ){  list << info; } //add to the list
898     }
899   if(!list.isEmpty()){ emit PlayFiles(list); }
900 }
901 
OpenWithApp(QAction * act)902 void DirWidget::OpenWithApp(QAction* act){
903   if(act->whatsThis().isEmpty()){ return; }
904   QStringList sel = currentBrowser()->currentSelection();
905   //Now we need to open the app file, and create the process
906   XDGDesktop desk(act->whatsThis());
907   QString exec = desk.generateExec(sel);
908   //qDebug() << "Launch App with selection:" << act->whatsThis() << sel;
909   //qDebug() << "Generating exec:" << exec;
910   ExternalProcess::launch(exec);
911 }
912 
autoExtractFiles()913 void DirWidget::autoExtractFiles(){
914   QStringList files = currentBrowser()->currentSelection();
915   qDebug() << "Starting auto-extract:" << files;
916   ExternalProcess::launch("lumina-archiver", QStringList() << "--ax" << files);
917 }
918 
919 
autoArchiveFiles()920 void DirWidget::autoArchiveFiles(){
921   QStringList files = currentBrowser()->currentSelection();
922   QString archive = QFileDialog::getSaveFileName(this, tr("Select Archive"), currentBrowser()->currentDirectory()+"/archive-"+QDateTime::currentDateTime().toString("yyyyMMdd_hhmm")+".tar.gz", currentBrowser()->currentDirectory());
923   if(archive.isEmpty()){ return; } //cancelled
924   qDebug() << "Starting auto-archival:" << archive << files;
925   ExternalProcess::launch("lumina-archiver", QStringList() << "--aa" << archive << files);
926 }
927 
setAsWallpaper()928 void DirWidget::setAsWallpaper(){
929   QStringList files = currentBrowser()->currentSelection();
930   //Get the screen for the wallpaper
931   QList<QScreen*> screens = QApplication::screens();
932   QString screenID;
933   if(screens.length()>1){
934     QStringList opts;
935      for(int i=0; i<screens.length(); i++){ opts << screens[i]->name(); }
936     screenID = QInputDialog::getItem(this, tr("Set Wallpaper on Screen"), tr("Screen"), opts, 0, false);
937     if(screenID.isEmpty()){ return; }
938   }else{
939     screenID = screens[0]->name();
940   }
941   //Now save this to the settings
942   QSettings deskset("lumina-desktop","desktopsettings");
943   deskset.setValue("desktop-"+screenID+"/background/filelist" , files);
944 }
945 
946 //====================
947 //         PROTECTED
948 //====================
mouseReleaseEvent(QMouseEvent * ev)949 void DirWidget::mouseReleaseEvent(QMouseEvent *ev){
950   static Qt::MouseButtons backmap = Qt::BackButton | Qt::ExtraButton5;
951   //qDebug() << "Mouse Click:" << ev->button();
952   if(backmap.testFlag(ev->button())){
953     ev->accept();
954     on_actionBack_triggered();
955   //}else if(ev->button()==Qt::ForwardButton()){
956     //ev->accept();
957   }else{
958     ev->ignore(); //not handled here
959   }
960 }
961 
openRootFM()962 void DirWidget::openRootFM(){
963   rootfmdir = "qsudo lumina-fm -new-instance " + currentDir();
964   qDebug() << "rootfmdir" << rootfmdir;
965   ExternalProcess::launch(rootfmdir);
966 }
967