1 /*
2 * File name: MainWindow.cpp
3 * Summary: QDirStat main window
4 * License: GPL V2 - See file LICENSE for details.
5 *
6 * Author: Stefan Hundhammer <Stefan.Hundhammer@gmx.de>
7 */
8
9
10 #include <QApplication>
11 #include <QCloseEvent>
12 #include <QMouseEvent>
13 #include <QMessageBox>
14 #include <QFileDialog>
15 #include <QClipboard>
16
17 #include "MainWindow.h"
18 #include "ActionManager.h"
19 #include "CleanupCollection.h"
20 #include "CleanupConfigPage.h"
21 #include "ConfigDialog.h"
22 #include "DataColumns.h"
23 #include "DebugHelpers.h"
24 #include "DirTree.h"
25 #include "DirTreeCache.h"
26 #include "DirTreeModel.h"
27 #include "Exception.h"
28 #include "ExcludeRules.h"
29 #include "FileDetailsView.h"
30 #include "FileSizeStatsWindow.h"
31 #include "FileTypeStatsWindow.h"
32 #include "Logger.h"
33 #include "MimeCategorizer.h"
34 #include "OpenDirDialog.h"
35 #include "OpenPkgDialog.h"
36 #include "OutputWindow.h"
37 #include "PanelMessage.h"
38 #include "PkgManager.h"
39 #include "PkgQuery.h"
40 #include "QDirStatApp.h"
41 #include "Refresher.h"
42 #include "SelectionModel.h"
43 #include "Settings.h"
44 #include "SettingsHelpers.h"
45 #include "SysUtil.h"
46 #include "Trash.h"
47 #include "UnreadableDirsWindow.h"
48 #include "Version.h"
49
50 #define LONG_MESSAGE 25*1000
51 #define UPDATE_MILLISEC 200
52
53 #define USE_CUSTOM_OPEN_DIR_DIALOG 1
54
55 using namespace QDirStat;
56
57
MainWindow()58 MainWindow::MainWindow():
59 QMainWindow(),
60 _ui( new Ui::MainWindow ),
61 _configDialog( 0 ),
62 _enableDirPermissionsWarning( false ),
63 _verboseSelection( false ),
64 _urlInWindowTitle( false ),
65 _useTreemapHover( false ),
66 _statusBarTimeout( 3000 ), // millisec
67 _treeLevelMapper(0),
68 _currentLayout( 0 )
69 {
70 CHECK_PTR( _ui );
71
72 _ui->setupUi( this );
73 ActionManager::instance()->addWidgetTree( this );
74 initLayoutActions();
75 createLayouts(); // see MainWindowLayout.cpp
76 readSettings();
77 _updateTimer.setInterval( UPDATE_MILLISEC );
78 _treeExpandTimer.setSingleShot( true );
79 _dUrl = _ui->actionDonate->iconText();
80 _futureSelection.setUseRootFallback( false );
81 _ui->menubar->setCornerWidget( new QLabel( MENUBAR_VERSION ) );
82
83 // The first call to app() creates the QDirStatApp and with it
84 // - the DirTreeModel
85 // - the DirTree (owned and managed by the DirTreeModel)
86 // - the SelectionModel
87 // - the CleanupCollection.
88
89 _ui->dirTreeView->setModel( app()->dirTreeModel() );
90 _ui->dirTreeView->setSelectionModel( app()->selectionModel() );
91
92 _ui->treemapView->setDirTree( app()->dirTree() );
93 _ui->treemapView->setSelectionModel( app()->selectionModel() );
94
95 app()->cleanupCollection()->addToMenu ( _ui->menuCleanup,
96 true ); // keepUpdated
97 app()->cleanupCollection()->addToToolBar( _ui->toolBar,
98 true ); // keepUpdated
99
100
101 _ui->dirTreeView->setCleanupCollection( app()->cleanupCollection() );
102 _ui->treemapView->setCleanupCollection( app()->cleanupCollection() );
103
104 _ui->breadcrumbNavigator->clear();
105
106 _historyButtons = new HistoryButtons( _ui->actionGoBack,
107 _ui->actionGoForward );
108 CHECK_NEW( _historyButtons );
109
110 _discoverActions = new DiscoverActions( this );
111 CHECK_NEW( _discoverActions );
112
113 #ifdef Q_OS_MACX
114 // This makes the application to look like more "native" on macOS
115 setUnifiedTitleAndToolBarOnMac( true );
116 _ui->toolBar->setMovable( false );
117 #endif
118
119 connectSignals();
120 connectMenuActions(); // see MainWindowMenus.cpp
121 changeLayout( _layoutName ); // see MainWindowLayout.cpp
122
123 checkPkgManagerSupport();
124
125 if ( ! _ui->actionShowTreemap->isChecked() )
126 _ui->treemapView->disable();
127
128 toggleVerboseSelection();
129 updateActions();
130 }
131
132
~MainWindow()133 MainWindow::~MainWindow()
134 {
135 // logDebug() << "Destroying main window" << endl;
136
137 if ( _currentLayout )
138 saveLayout( _currentLayout ); // see MainWindowLayout.cpp
139
140 writeSettings();
141 ExcludeRules::instance()->writeSettings();
142 MimeCategorizer::instance()->writeSettings();
143
144 // Relying on the QObject hierarchy to properly clean this up resulted in a
145 // segfault; there was probably a problem in the deletion order.
146
147 if ( _configDialog )
148 delete _configDialog;
149
150 delete _ui->dirTreeView;
151 delete _ui;
152 delete _historyButtons;
153
154 qDeleteAll( _layouts );
155
156 QDirStatApp::deleteInstance();
157
158 // logDebug() << "Main window destroyed" << endl;
159 }
160
161
checkPkgManagerSupport()162 void MainWindow::checkPkgManagerSupport()
163 {
164 if ( ! PkgQuery::haveGetInstalledPkgSupport() ||
165 ! PkgQuery::haveFileListSupport() )
166 {
167 logInfo() << "No package manager support "
168 << "for getting installed packages or file lists"
169 << endl;
170
171 _ui->actionOpenPkg->setEnabled( false );
172 }
173
174 PkgManager * pkgManager = PkgQuery::primaryPkgManager();
175
176 if ( ! pkgManager || ! pkgManager->supportsFileListCache() )
177 {
178 logInfo() << "No package manager support "
179 << "for getting a file lists cache"
180 << endl;
181
182 _ui->actionShowUnpkgFiles->setEnabled( false );
183 }
184 }
185
186
connectSignals()187 void MainWindow::connectSignals()
188 {
189 connect( app()->selectionModel(), SIGNAL( currentBranchChanged( QModelIndex ) ),
190 _ui->dirTreeView, SLOT ( closeAllExcept ( QModelIndex ) ) );
191
192 connect( app()->dirTree(), SIGNAL( startingReading() ),
193 this, SLOT ( startingReading() ) );
194
195 connect( app()->dirTree(), SIGNAL( finished() ),
196 this, SLOT ( readingFinished() ) );
197
198 connect( app()->dirTree(), SIGNAL( aborted() ),
199 this, SLOT ( readingAborted() ) );
200
201 connect( app()->selectionModel(), SIGNAL( selectionChanged() ),
202 this, SLOT ( updateActions() ) );
203
204 connect( app()->selectionModel(), SIGNAL( currentItemChanged( FileInfo *, FileInfo * ) ),
205 this, SLOT ( updateActions() ) );
206
207 connect( app()->selectionModel(), SIGNAL( currentItemChanged( FileInfo *, FileInfo * ) ),
208 _ui->breadcrumbNavigator, SLOT ( setPath ( FileInfo * ) ) );
209
210 connect( app()->selectionModel(), SIGNAL( currentItemChanged( FileInfo *, FileInfo * ) ),
211 _historyButtons, SLOT ( addToHistory ( FileInfo * ) ) );
212
213 connect( _historyButtons, SIGNAL( navigateToUrl( QString ) ),
214 this, SLOT ( navigateToUrl( QString ) ) );
215
216 connect( _ui->breadcrumbNavigator, SIGNAL( pathClicked ( QString ) ),
217 app()->selectionModel(), SLOT ( setCurrentItem( QString ) ) );
218
219 connect( _ui->treemapView, SIGNAL( treemapChanged() ),
220 this, SLOT ( updateActions() ) );
221
222 connect( app()->cleanupCollection(), SIGNAL( startingCleanup( QString ) ),
223 this, SLOT ( startingCleanup( QString ) ) );
224
225 connect( app()->cleanupCollection(), SIGNAL( cleanupFinished( int ) ),
226 this, SLOT ( cleanupFinished( int ) ) );
227
228 connect( &_updateTimer, SIGNAL( timeout() ),
229 this, SLOT ( showElapsedTime() ) );
230
231 connect( &_treeExpandTimer, SIGNAL( timeout() ),
232 _ui->actionExpandTreeLevel1, SLOT( trigger() ) );
233
234 if ( _useTreemapHover )
235 {
236 connect( _ui->treemapView, SIGNAL( hoverEnter ( FileInfo * ) ),
237 this, SLOT ( showCurrent( FileInfo * ) ) );
238
239 connect( _ui->treemapView, SIGNAL( hoverLeave ( FileInfo * ) ),
240 this, SLOT ( showSummary() ) );
241 }
242
243 connect( app()->selectionModel(), SIGNAL( selectionChanged() ),
244 this, SLOT ( selectionChanged() ) );
245
246 connect( app()->selectionModel(), SIGNAL( currentItemChanged( FileInfo *, FileInfo * ) ),
247 this, SLOT ( currentItemChanged( FileInfo *, FileInfo * ) ) );
248 }
249
250
updateActions()251 void MainWindow::updateActions()
252 {
253 bool reading = app()->dirTree()->isBusy();
254 FileInfo * currentItem = app()->selectionModel()->currentItem();
255 FileInfo * firstToplevel = app()->dirTree()->firstToplevel();
256 bool pkgView = firstToplevel && firstToplevel->isPkgInfo();
257
258 _ui->actionStopReading->setEnabled( reading );
259 _ui->actionRefreshAll->setEnabled ( ! reading );
260 _ui->actionAskReadCache->setEnabled ( ! reading );
261 _ui->actionAskWriteCache->setEnabled( ! reading );
262
263 _ui->actionCopyPathToClipboard->setEnabled( currentItem );
264 _ui->actionGoUp->setEnabled( currentItem && currentItem->treeLevel() > 1 );
265 _ui->actionGoToToplevel->setEnabled( firstToplevel && ( ! currentItem || currentItem->treeLevel() > 1 ));
266
267 FileInfoSet selectedItems = app()->selectionModel()->selectedItems();
268 FileInfo * sel = selectedItems.first();
269 int selSize = selectedItems.size();
270
271 bool oneDirSelected = selSize == 1 && sel && sel->isDir() && ! sel->isPkgInfo();
272 bool pseudoDirSelected = selectedItems.containsPseudoDir();
273 bool pkgSelected = selectedItems.containsPkg();
274
275 _ui->actionMoveToTrash->setEnabled( sel && ! pseudoDirSelected && ! pkgSelected && ! reading );
276 _ui->actionRefreshSelected->setEnabled( selSize == 1 && ! sel->isExcluded() && ! sel->isMountPoint() && ! pkgView );
277 _ui->actionContinueReadingAtMountPoint->setEnabled( oneDirSelected && sel->isMountPoint() );
278 _ui->actionReadExcludedDirectory->setEnabled ( oneDirSelected && sel->isExcluded() );
279
280 bool nothingOrOneDir = selectedItems.isEmpty() || oneDirSelected;
281
282 _ui->actionFileSizeStats->setEnabled( ! reading && nothingOrOneDir );
283 _ui->actionFileTypeStats->setEnabled( ! reading && nothingOrOneDir );
284 _ui->actionFileAgeStats->setEnabled ( ! reading && nothingOrOneDir );
285
286 bool showingTreemap = _ui->treemapView->isVisible();
287
288 _ui->actionTreemapAsSidePanel->setEnabled( showingTreemap );
289 _ui->actionTreemapZoomIn->setEnabled ( showingTreemap && _ui->treemapView->canZoomIn() );
290 _ui->actionTreemapZoomOut->setEnabled ( showingTreemap && _ui->treemapView->canZoomOut() );
291 _ui->actionResetTreemapZoom->setEnabled( showingTreemap && _ui->treemapView->canZoomOut() );
292 _ui->actionTreemapRebuild->setEnabled ( showingTreemap );
293
294 _historyButtons->updateActions();
295 }
296
297
readSettings()298 void MainWindow::readSettings()
299 {
300 QDirStat::Settings settings;
301 settings.beginGroup( "MainWindow" );
302
303 _statusBarTimeout = settings.value( "StatusBarTimeoutMillisec", 3000 ).toInt();
304 bool showTreemap = settings.value( "ShowTreemap" , true ).toBool();
305 bool treemapOnSide = settings.value( "TreemapOnSide" , false ).toBool();
306
307 _verboseSelection = settings.value( "VerboseSelection" , false ).toBool();
308 _urlInWindowTitle = settings.value( "UrlInWindowTitle" , false ).toBool();
309 _useTreemapHover = settings.value( "UseTreemapHover" , false ).toBool();
310 _layoutName = settings.value( "Layout" , "L2" ).toString();
311
312 settings.endGroup();
313
314 settings.beginGroup( "MainWindow-Subwindows" );
315 QByteArray mainSplitterState = settings.value( "MainSplitter" , QByteArray() ).toByteArray();
316 QByteArray topSplitterState = settings.value( "TopSplitter" , QByteArray() ).toByteArray();
317 settings.endGroup();
318
319 _ui->actionShowTreemap->setChecked( showTreemap );
320 _ui->actionTreemapAsSidePanel->setChecked( treemapOnSide );
321 treemapAsSidePanel();
322
323 _ui->actionVerboseSelection->setChecked( _verboseSelection );
324
325 foreach ( QAction * action, _layoutActionGroup->actions() )
326 {
327 if ( action->data().toString() == _layoutName )
328 action->setChecked( true );
329 }
330
331 readWindowSettings( this, "MainWindow" );
332
333 if ( ! mainSplitterState.isNull() )
334 _ui->mainWinSplitter->restoreState( mainSplitterState );
335
336 if ( ! topSplitterState.isNull() )
337 _ui->topViewsSplitter->restoreState( topSplitterState );
338 else
339 {
340 // The Qt designer refuses to let me set a reasonable size for that
341 // widget, so let's set one here. Yes, that's not really how this is
342 // supposed to be, but I am fed up with that stuff.
343
344 _ui->fileDetailsPanel->resize( QSize( 300, 300 ) );
345 }
346
347 foreach ( TreeLayout * layout, _layouts )
348 readLayoutSettings( layout ); // see MainWindowLayout.cpp
349
350 ExcludeRules::instance()->readSettings();
351 Debug::dumpExcludeRules();
352 }
353
354
writeSettings()355 void MainWindow::writeSettings()
356 {
357 QDirStat::Settings settings;
358 settings.beginGroup( "MainWindow" );
359
360 settings.setValue( "ShowTreemap" , _ui->actionShowTreemap->isChecked() );
361 settings.setValue( "TreemapOnSide" , _ui->actionTreemapAsSidePanel->isChecked() );
362 settings.setValue( "VerboseSelection", _verboseSelection );
363 settings.setValue( "Layout" , _layoutName );
364
365 // Those are only set if not already in the settings (they might have been
366 // set from a config dialog).
367 settings.setDefaultValue( "StatusBarTimeoutMillisec", _statusBarTimeout );
368 settings.setDefaultValue( "UrlInWindowTitle" , _urlInWindowTitle );
369 settings.setDefaultValue( "UseTreemapHover" , _useTreemapHover );
370
371 settings.endGroup();
372
373 writeWindowSettings( this, "MainWindow" );
374
375 settings.beginGroup( "MainWindow-Subwindows" );
376 settings.setValue( "MainSplitter" , _ui->mainWinSplitter->saveState() );
377 settings.setValue( "TopSplitter" , _ui->topViewsSplitter->saveState() );
378 settings.endGroup();
379
380 foreach ( TreeLayout * layout, _layouts )
381 writeLayoutSettings( layout ); // see MainWindowLayout.cpp
382 }
383
384
showTreemapView()385 void MainWindow::showTreemapView()
386 {
387 if ( _ui->actionShowTreemap->isChecked() )
388 _ui->treemapView->enable();
389 else
390 _ui->treemapView->disable();
391 }
392
393
treemapAsSidePanel()394 void MainWindow::treemapAsSidePanel()
395 {
396 if ( _ui->actionTreemapAsSidePanel->isChecked() )
397 _ui->mainWinSplitter->setOrientation( Qt::Horizontal );
398 else
399 _ui->mainWinSplitter->setOrientation( Qt::Vertical );
400 }
401
402
busyDisplay()403 void MainWindow::busyDisplay()
404 {
405 _ui->treemapView->disable();
406 updateActions();
407
408 // If it is open, close the window that lists unreadable directories:
409 // With the next directory read, things might have changed; the user may
410 // have fixed permissions or ownership of those directories.
411
412 UnreadableDirsWindow::closeSharedInstance();
413
414 if ( _dirPermissionsWarning )
415 _dirPermissionsWarning->deleteLater();
416
417 _updateTimer.start();
418
419 // It would be nice to sort by read jobs during reading, but this confuses
420 // the hell out of the Qt side of the data model; so let's sort by name
421 // instead.
422
423 int sortCol = QDirStat::DataColumns::toViewCol( QDirStat::NameCol );
424 _ui->dirTreeView->sortByColumn( sortCol, Qt::AscendingOrder );
425
426 if ( ! PkgFilter::isPkgUrl( app()->dirTree()->url() ) &&
427 ! app()->selectionModel()->currentBranch() )
428 {
429 _treeExpandTimer.start( 200 );
430 // This will trigger actionExpandTreeLevel1. Hopefully after those 200
431 // millisec there will be some items in the tree to expand.
432 }
433 }
434
435
idleDisplay()436 void MainWindow::idleDisplay()
437 {
438 logInfo() << endl;
439
440 updateActions();
441 _updateTimer.stop();
442 int sortCol = QDirStat::DataColumns::toViewCol( QDirStat::PercentNumCol );
443 _ui->dirTreeView->sortByColumn( sortCol, Qt::DescendingOrder );
444
445 if ( ! _futureSelection.isEmpty() )
446 {
447 _treeExpandTimer.stop();
448 applyFutureSelection();
449 }
450 else if ( ! app()->selectionModel()->currentBranch() )
451 {
452 logDebug() << "No current branch - expanding tree to level 1" << endl;
453 expandTreeToLevel( 1 );
454 }
455
456 updateFileDetailsView();
457 showTreemapView();
458 }
459
460
updateFileDetailsView()461 void MainWindow::updateFileDetailsView()
462 {
463 if ( _ui->fileDetailsView->isVisible() )
464 {
465 FileInfoSet sel = app()->selectionModel()->selectedItems();
466
467 if ( sel.isEmpty() )
468 _ui->fileDetailsView->showDetails( app()->selectionModel()->currentItem() );
469 else
470 {
471 if ( sel.count() == 1 )
472 _ui->fileDetailsView->showDetails( sel.first() );
473 else
474 _ui->fileDetailsView->showDetails( sel );
475 }
476 }
477 }
478
479
startingReading()480 void MainWindow::startingReading()
481 {
482 _stopWatch.start();
483 busyDisplay();
484 }
485
486
readingFinished()487 void MainWindow::readingFinished()
488 {
489 logInfo() << endl;
490
491 idleDisplay();
492
493 QString elapsedTime = formatMillisec( _stopWatch.elapsed() );
494 _ui->statusBar->showMessage( tr( "Finished. Elapsed time: %1").arg( elapsedTime ), LONG_MESSAGE );
495 logInfo() << "Reading finished after " << elapsedTime << endl;
496
497 if ( app()->dirTree()->firstToplevel() &&
498 app()->dirTree()->firstToplevel()->errSubDirCount() > 0 )
499 {
500 showDirPermissionsWarning();
501 }
502
503 // Debug::dumpModelTree( app()->dirTreeModel(), QModelIndex(), "" );
504 }
505
506
readingAborted()507 void MainWindow::readingAborted()
508 {
509 logInfo() << endl;
510
511 idleDisplay();
512 QString elapsedTime = formatMillisec( _stopWatch.elapsed() );
513 _ui->statusBar->showMessage( tr( "Aborted. Elapsed time: %1").arg( elapsedTime ), LONG_MESSAGE );
514 logInfo() << "Reading aborted after " << elapsedTime << endl;
515 }
516
517
openUrl(const QString & url)518 void MainWindow::openUrl( const QString & url )
519 {
520 _enableDirPermissionsWarning = true;
521 _historyButtons->clearHistory();
522
523 if ( PkgFilter::isPkgUrl( url ) )
524 readPkg( url );
525 else if ( isUnpkgUrl( url ) )
526 showUnpkgFiles( url ); // see MainWinUnpkg.cpp
527 else
528 openDir( url );
529 }
530
531
openDir(const QString & url)532 void MainWindow::openDir( const QString & url )
533 {
534 try
535 {
536 app()->dirTreeModel()->openUrl( url );
537 updateWindowTitle( app()->dirTree()->url() );
538 }
539 catch ( const SysCallFailedException & ex )
540 {
541 CAUGHT( ex );
542 showOpenDirErrorPopup( ex );
543 askOpenDir();
544 }
545
546 updateActions();
547 expandTreeToLevel( 1 );
548 }
549
550
showOpenDirErrorPopup(const SysCallFailedException & ex)551 void MainWindow::showOpenDirErrorPopup( const SysCallFailedException & ex )
552 {
553 updateWindowTitle( "" );
554 app()->dirTree()->sendFinished();
555
556 QMessageBox errorPopup( QMessageBox::Warning, // icon
557 tr( "Error" ), // title
558 tr( "Could not open directory %1" ).arg( ex.resourceName() ), // text
559 QMessageBox::Ok, // buttons
560 this ); // parent
561 errorPopup.setDetailedText( ex.what() );
562 errorPopup.exec();
563 }
564
565
askOpenDir()566 void MainWindow::askOpenDir()
567 {
568 QString path;
569 DirTree * tree = app()->dirTree();
570 bool crossFilesystems = tree->crossFilesystems();
571
572 #if USE_CUSTOM_OPEN_DIR_DIALOG
573 path = QDirStat::OpenDirDialog::askOpenDir( &crossFilesystems, this );
574 #else
575 path = QFileDialog::getExistingDirectory( this, // parent
576 tr("Select directory to scan") );
577 #endif
578
579 if ( ! path.isEmpty() )
580 {
581 tree->reset();
582 tree->setCrossFilesystems( crossFilesystems );
583 openUrl( path );
584 }
585 }
586
587
askOpenPkg()588 void MainWindow::askOpenPkg()
589 {
590 bool canceled;
591 PkgFilter pkgFilter = OpenPkgDialog::askPkgFilter( &canceled );
592
593 if ( ! canceled )
594 {
595 app()->dirTree()->reset();
596 readPkg( pkgFilter );
597 }
598 }
599
600
readPkg(const PkgFilter & pkgFilter)601 void MainWindow::readPkg( const PkgFilter & pkgFilter )
602 {
603 // logInfo() << "URL: " << pkgFilter.url() << endl;
604
605 updateWindowTitle( pkgFilter.url() );
606 expandTreeToLevel( 0 ); // Performance boost: Down from 25 to 6 sec.
607 app()->dirTreeModel()->readPkg( pkgFilter );
608 }
609
610
refreshAll()611 void MainWindow::refreshAll()
612 {
613 _enableDirPermissionsWarning = true;
614 QString url = app()->dirTree()->url();
615
616 if ( ! url.isEmpty() )
617 {
618 logDebug() << "Refreshing " << url << endl;
619
620 if ( PkgFilter::isPkgUrl( url ) )
621 app()->dirTreeModel()->readPkg( url );
622 else
623 app()->dirTreeModel()->openUrl( url );
624
625 // No need to check if the URL is an unpkg:/ URL:
626 //
627 // In that case, the previous filters are still set, and just reading
628 // the dir tree again from disk with openUrl() will filter out the
629 // unwanted packaged files, ignored extensions and excluded directories
630 // again.
631
632 updateActions();
633 }
634 else
635 {
636 askOpenDir();
637 }
638 }
639
640
refreshSelected()641 void MainWindow::refreshSelected()
642 {
643 busyDisplay();
644 _futureSelection.set( app()->selectionModel()->selectedItems().first() );
645 // logDebug() << "Setting future selection: " << _futureSelection.subtree() << endl;
646 app()->dirTreeModel()->refreshSelected();
647 updateActions();
648 }
649
650
applyFutureSelection()651 void MainWindow::applyFutureSelection()
652 {
653 FileInfo * sel = _futureSelection.subtree();
654 // logDebug() << "Using future selection: " << sel << endl;
655
656 if ( sel )
657 {
658 _treeExpandTimer.stop();
659 _futureSelection.clear();
660 app()->selectionModel()->setCurrentBranch( sel );
661
662 if ( sel->isMountPoint() )
663 _ui->dirTreeView->setExpanded( sel, true );
664 }
665 }
666
667
stopReading()668 void MainWindow::stopReading()
669 {
670 if ( app()->dirTree()->isBusy() )
671 {
672 app()->dirTree()->abortReading();
673 _ui->statusBar->showMessage( tr( "Reading aborted." ), LONG_MESSAGE );
674 }
675 }
676
677
readCache(const QString & cacheFileName)678 void MainWindow::readCache( const QString & cacheFileName )
679 {
680 app()->dirTreeModel()->clear();
681 _historyButtons->clearHistory();
682
683 if ( ! cacheFileName.isEmpty() )
684 app()->dirTree()->readCache( cacheFileName );
685 }
686
687
askReadCache()688 void MainWindow::askReadCache()
689 {
690 QString fileName = QFileDialog::getOpenFileName( this, // parent
691 tr( "Select QDirStat cache file" ),
692 DEFAULT_CACHE_NAME );
693 if ( ! fileName.isEmpty() )
694 readCache( fileName );
695
696 updateActions();
697 }
698
699
askWriteCache()700 void MainWindow::askWriteCache()
701 {
702 QString fileName = QFileDialog::getSaveFileName( this, // parent
703 tr( "Enter name for QDirStat cache file"),
704 DEFAULT_CACHE_NAME );
705 if ( ! fileName.isEmpty() )
706 {
707 bool ok = app()->dirTree()->writeCache( fileName );
708
709 if ( ok )
710 {
711 showProgress( tr( "Directory tree written to file %1" ).arg( fileName ) );
712 }
713 else
714 {
715 QMessageBox::critical( this,
716 tr( "Error" ), // Title
717 tr( "ERROR writing cache file %1").arg( fileName ) );
718 }
719 }
720 }
721
722
updateWindowTitle(const QString & url)723 void MainWindow::updateWindowTitle( const QString & url )
724 {
725 QString windowTitle = "QDirStat";
726
727 if ( SysUtil::runningAsRoot() )
728 windowTitle += tr( " [root]" );
729
730 if ( _urlInWindowTitle )
731 windowTitle += " " + url;
732
733 setWindowTitle( windowTitle );
734 }
735
736
showProgress(const QString & text)737 void MainWindow::showProgress( const QString & text )
738 {
739 _ui->statusBar->showMessage( text, _statusBarTimeout );
740 }
741
742
showElapsedTime()743 void MainWindow::showElapsedTime()
744 {
745 showProgress( tr( "Reading... %1" )
746 .arg( formatMillisec( _stopWatch.elapsed(), false ) ) );
747 }
748
749
showCurrent(FileInfo * item)750 void MainWindow::showCurrent( FileInfo * item )
751 {
752 if ( item )
753 {
754 QString msg = QString( "%1 (%2%3)" )
755 .arg( item->debugUrl() )
756 .arg( item->sizePrefix() )
757 .arg( formatSize( item->totalSize() ) );
758
759 if ( item->readState() == DirPermissionDenied )
760 msg += tr( " [Permission Denied]" );
761 else if ( item->readState() == DirError )
762 msg += tr( " [Read Error]" );
763
764 _ui->statusBar->showMessage( msg );
765 }
766 else
767 {
768 _ui->statusBar->clearMessage();
769 }
770 }
771
772
showSummary()773 void MainWindow::showSummary()
774 {
775 FileInfoSet sel = app()->selectionModel()->selectedItems();
776 int count = sel.size();
777
778 if ( count <= 1 )
779 showCurrent( app()->selectionModel()->currentItem() );
780 else
781 {
782 sel = sel.normalized();
783
784 _ui->statusBar->showMessage( tr( "%1 items selected (%2 total)" )
785 .arg( count )
786 .arg( formatSize( sel.totalSize() ) ) );
787 }
788 }
789
790
startingCleanup(const QString & cleanupName)791 void MainWindow::startingCleanup( const QString & cleanupName )
792 {
793 showProgress( tr( "Starting cleanup action %1" ).arg( cleanupName ) );
794 }
795
796
cleanupFinished(int errorCount)797 void MainWindow::cleanupFinished( int errorCount )
798 {
799 logDebug() << "Error count: " << errorCount << endl;
800
801 if ( errorCount == 0 )
802 showProgress( tr( "Cleanup action finished successfully." ) );
803 else
804 showProgress( tr( "Cleanup action finished with %1 errors." ).arg( errorCount ) );
805 }
806
807
notImplemented()808 void MainWindow::notImplemented()
809 {
810 QMessageBox::warning( this, tr( "Error" ), tr( "Not implemented!" ) );
811 }
812
813
copyCurrentPathToClipboard()814 void MainWindow::copyCurrentPathToClipboard()
815 {
816 FileInfo * currentItem = app()->selectionModel()->currentItem();
817
818 if ( currentItem )
819 {
820 QClipboard * clipboard = QApplication::clipboard();
821 QString path = currentItem->path();
822 clipboard->setText( path );
823 showProgress( tr( "Copied to system clipboard: %1" ).arg( path ) );
824 }
825 else
826 {
827 showProgress( tr( "No current item" ) );
828 }
829 }
830
831
expandTreeToLevel(int level)832 void MainWindow::expandTreeToLevel( int level )
833 {
834 logDebug() << "Expanding tree to level " << level << endl;
835
836 if ( level < 1 )
837 _ui->dirTreeView->collapseAll();
838 else
839 _ui->dirTreeView->expandToDepth( level - 1 );
840 }
841
842
navigateUp()843 void MainWindow::navigateUp()
844 {
845 FileInfo * currentItem = app()->selectionModel()->currentItem();
846
847 if ( currentItem && currentItem->parent() &&
848 currentItem->parent() != app()->dirTree()->root() )
849 {
850 app()->selectionModel()->setCurrentItem( currentItem->parent(),
851 true ); // select
852 }
853 }
854
855
navigateToToplevel()856 void MainWindow::navigateToToplevel()
857 {
858 FileInfo * toplevel = app()->dirTree()->firstToplevel();
859
860 if ( toplevel )
861 {
862 expandTreeToLevel( 1 );
863 app()->selectionModel()->setCurrentItem( toplevel,
864 true ); // select
865 }
866 }
867
868
navigateToUrl(const QString & url)869 void MainWindow::navigateToUrl( const QString & url )
870 {
871 // logDebug() << "Navigating to " << url << endl;
872
873 if ( ! url.isEmpty() )
874 {
875 FileInfo * sel = app()->dirTree()->locate( url,
876 true ); // findPseudoDirs
877
878 if ( sel )
879
880 {
881 app()->selectionModel()->setCurrentItem( sel,
882 true ); // select
883 _ui->dirTreeView->setExpanded( sel, true );
884 }
885 }
886 }
887
888
moveToTrash()889 void MainWindow::moveToTrash()
890 {
891 FileInfoSet selectedItems = app()->selectionModel()->selectedItems().normalized();
892
893 // Prepare output window
894
895 OutputWindow * outputWindow = new OutputWindow( qApp->activeWindow() );
896 CHECK_NEW( outputWindow );
897
898 // Prepare refresher
899
900 FileInfoSet refreshSet = Refresher::parents( selectedItems );
901 app()->selectionModel()->prepareRefresh( refreshSet );
902 Refresher * refresher = new Refresher( refreshSet, this );
903 CHECK_NEW( refresher );
904
905 connect( outputWindow, SIGNAL( lastProcessFinished( int ) ),
906 refresher, SLOT ( refresh() ) );
907
908 outputWindow->showAfterTimeout();
909
910 // Move all selected items to trash
911
912 foreach ( FileInfo * item, selectedItems )
913 {
914 bool success = Trash::trash( item->path() );
915
916 if ( success )
917 outputWindow->addStdout( tr( "Moved to trash: %1" ).arg( item->path() ) );
918 else
919 outputWindow->addStderr( tr( "Move to trash failed for %1" ).arg( item->path() ) );
920 }
921
922 outputWindow->noMoreProcesses();
923 }
924
925
openConfigDialog()926 void MainWindow::openConfigDialog()
927 {
928 if ( _configDialog && _configDialog->isVisible() )
929 return;
930
931 // For whatever crazy reason it is considerably faster to delete that
932 // complex dialog and recreate it from scratch than to simply leave it
933 // alive and just show it again. Well, whatever - so be it.
934 //
935 // And yes, I added debug logging here, in the dialog's setup(), in
936 // showEvent(); I added update(). No result whatsoever.
937 // Okay, then let's take the long way around.
938
939 if ( _configDialog )
940 delete _configDialog;
941
942 _configDialog = new ConfigDialog( this );
943 CHECK_PTR( _configDialog );
944 _configDialog->cleanupConfigPage()->setCleanupCollection( app()->cleanupCollection() );
945
946 if ( ! _configDialog->isVisible() )
947 {
948 _configDialog->setup();
949 _configDialog->show();
950 }
951 }
952
953
showFileTypeStats()954 void MainWindow::showFileTypeStats()
955 {
956 FileTypeStatsWindow::populateSharedInstance( app()->selectedDirOrRoot() );
957 }
958
959
showFileSizeStats()960 void MainWindow::showFileSizeStats()
961 {
962 FileSizeStatsWindow::populateSharedInstance( app()->selectedDirOrRoot() );
963 }
964
965
showFileAgeStats()966 void MainWindow::showFileAgeStats()
967 {
968 if ( ! _fileAgeStatsWindow )
969 {
970 // This deletes itself when the user closes it. The associated QPointer
971 // keeps track of that and sets the pointer to 0 when it happens.
972
973 _fileAgeStatsWindow = new FileAgeStatsWindow( this );
974
975 connect( app()->selectionModel(), SIGNAL( currentItemChanged( FileInfo *, FileInfo * ) ),
976 _fileAgeStatsWindow, SLOT ( syncedPopulate ( FileInfo * ) ) );
977
978 connect( _fileAgeStatsWindow, SIGNAL( locateFilesFromYear ( QString, short ) ),
979 _discoverActions, SLOT ( discoverFilesFromYear ( QString, short ) ) );
980
981 connect( _fileAgeStatsWindow, SIGNAL( locateFilesFromMonth ( QString, short, short ) ),
982 _discoverActions, SLOT ( discoverFilesFromMonth( QString, short, short ) ) );
983 }
984
985 _fileAgeStatsWindow->populate( app()->selectedDirOrRoot() );
986 _fileAgeStatsWindow->show();
987 }
988
989
showFilesystems()990 void MainWindow::showFilesystems()
991 {
992 if ( ! _filesystemsWindow )
993 {
994 // This deletes itself when the user closes it. The associated QPointer
995 // keeps track of that and sets the pointer to 0 when it happens.
996
997 _filesystemsWindow = new FilesystemsWindow( this );
998
999 connect( _filesystemsWindow, SIGNAL( readFilesystem( QString ) ),
1000 this, SLOT ( openUrl ( QString ) ) );
1001 }
1002
1003 _filesystemsWindow->populate();
1004 _filesystemsWindow->show();
1005 }
1006
1007
showDirPermissionsWarning()1008 void MainWindow::showDirPermissionsWarning()
1009 {
1010 if ( _dirPermissionsWarning || ! _enableDirPermissionsWarning )
1011 return;
1012
1013 PanelMessage * msg = new PanelMessage( _ui->messagePanel );
1014 CHECK_NEW( msg );
1015
1016 msg->setHeading( tr( "Some directories could not be read." ) );
1017 msg->setText( tr( "You might not have sufficient permissions." ) );
1018 msg->setIcon( QPixmap( ":/icons/lock-closed.png" ) );
1019
1020 msg->connectDetailsLink( this, SLOT( showUnreadableDirs() ) );
1021
1022 _ui->messagePanel->add( msg );
1023 _dirPermissionsWarning = msg;
1024 _enableDirPermissionsWarning = false;
1025 }
1026
1027
showUnreadableDirs()1028 void MainWindow::showUnreadableDirs()
1029 {
1030 UnreadableDirsWindow::populateSharedInstance( app()->dirTree()->root() );
1031 }
1032
1033
selectionChanged()1034 void MainWindow::selectionChanged()
1035 {
1036 showSummary();
1037 updateFileDetailsView();
1038
1039 if ( _verboseSelection )
1040 {
1041 logNewline();
1042 app()->selectionModel()->dumpSelectedItems();
1043 }
1044 }
1045
1046
currentItemChanged(FileInfo * newCurrent,FileInfo * oldCurrent)1047 void MainWindow::currentItemChanged( FileInfo * newCurrent, FileInfo * oldCurrent )
1048 {
1049 showSummary();
1050
1051 if ( ! oldCurrent )
1052 updateFileDetailsView();
1053
1054 if ( _verboseSelection )
1055 {
1056 logDebug() << "new current: " << newCurrent << endl;
1057 logDebug() << "old current: " << oldCurrent << endl;
1058 app()->selectionModel()->dumpSelectedItems();
1059 }
1060 }
1061
1062
mousePressEvent(QMouseEvent * event)1063 void MainWindow::mousePressEvent( QMouseEvent * event )
1064 {
1065 if ( event )
1066 {
1067 QAction * action = 0;
1068
1069 switch ( event->button() )
1070 {
1071 // Handle the back / forward buttons on the mouse to act like the
1072 // history back / forward buttons in the tool bar
1073
1074 case Qt::BackButton:
1075 // logDebug() << "BackButton" << endl;
1076 action = _ui->actionGoBack;
1077 break;
1078
1079 case Qt::ForwardButton:
1080 // logDebug() << "ForwardButton" << endl;
1081 action = _ui->actionGoForward;
1082 break;
1083
1084 default:
1085 QMainWindow::mousePressEvent( event );
1086 break;
1087 }
1088
1089 if ( action )
1090 {
1091 if ( action->isEnabled() )
1092 action->trigger();
1093 }
1094 }
1095 }
1096
1097
1098
1099
1100 //---------------------------------------------------------------------------
1101 // Debugging Helpers
1102 //---------------------------------------------------------------------------
1103
1104
toggleVerboseSelection()1105 void MainWindow::toggleVerboseSelection()
1106 {
1107 // Verbose selection is toggled with Shift-F7
1108
1109 _verboseSelection = _ui->actionVerboseSelection->isChecked();
1110
1111 if ( app()->selectionModel() )
1112 app()->selectionModel()->setVerbose( _verboseSelection );
1113
1114 logInfo() << "Verbose selection is now " << ( _verboseSelection ? "on" : "off" )
1115 << ". Change this with Shift-F7." << endl;
1116 }
1117
1118
itemClicked(const QModelIndex & index)1119 void MainWindow::itemClicked( const QModelIndex & index )
1120 {
1121 if ( ! _verboseSelection )
1122 return;
1123
1124 if ( index.isValid() )
1125 {
1126 FileInfo * item = static_cast<FileInfo *>( index.internalPointer() );
1127
1128 logDebug() << "Clicked row " << index.row()
1129 << " col " << index.column()
1130 << " (" << QDirStat::DataColumns::fromViewCol( index.column() ) << ")"
1131 << "\t" << item
1132 << endl;
1133 // << " data(0): " << index.model()->data( index, 0 ).toString()
1134 // logDebug() << "Ancestors: " << Debug::modelTreeAncestors( index ).join( " -> " ) << endl;
1135 }
1136 else
1137 {
1138 logDebug() << "Invalid model index" << endl;
1139 }
1140
1141 // app()->dirTreeModel()->dumpPersistentIndexList();
1142 }
1143
1144
1145 // For more MainWindow:: methods, See also:
1146 //
1147 // - MainWindowHelp.cpp
1148 // - MainWindowLayout.cpp
1149 // - MainWindowMenus.cpp
1150 // - MainWindowUnpkg.cpp
1151
1152