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