xref: /glogg/src/mainwindow.cpp (revision 8570d8d2e06825e8052e83ff8b2f491f6c476d23)
1 /*
2  * Copyright (C) 2009, 2010, 2011, 2013, 2014 Nicolas Bonnefon and other contributors
3  *
4  * This file is part of glogg.
5  *
6  * glogg is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * glogg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with glogg.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 // This file implements MainWindow. It is responsible for creating and
21 // managing the menus, the toolbar, and the CrawlerWidget. It also
22 // load/save the settings on opening/closing of the app
23 
24 #include <iostream>
25 #include <cassert>
26 
27 #include <QAction>
28 #include <QDesktopWidget>
29 #include <QMenuBar>
30 #include <QToolBar>
31 #include <QFileInfo>
32 #include <QFileDialog>
33 #include <QClipboard>
34 #include <QMessageBox>
35 #include <QCloseEvent>
36 #include <QDragEnterEvent>
37 #include <QMimeData>
38 #include <QUrl>
39 
40 #include "log.h"
41 
42 #include "mainwindow.h"
43 
44 #include "sessioninfo.h"
45 #include "recentfiles.h"
46 #include "crawlerwidget.h"
47 #include "filtersdialog.h"
48 #include "optionsdialog.h"
49 #include "persistentinfo.h"
50 #include "menuactiontooltipbehavior.h"
51 #include "tabbedcrawlerwidget.h"
52 
53 // Returns the size in human readable format
54 static QString readableSize( qint64 size );
55 
56 MainWindow::MainWindow( std::unique_ptr<Session> session ) :
57     session_( std::move( session )  ),
58     recentFiles( Persistent<RecentFiles>( "recentFiles" ) ),
59     mainIcon_(),
60     signalMux_(),
61     quickFindMux_( session_->getQuickFindPattern() ),
62     mainTabWidget_()
63 {
64     createActions();
65     createMenus();
66     // createContextMenu();
67     createToolBars();
68     // createStatusBar();
69 
70     setAcceptDrops( true );
71 
72     // Default geometry
73     const QRect geometry = QApplication::desktop()->availableGeometry( this );
74     setGeometry( geometry.x() + 20, geometry.y() + 40,
75             geometry.width() - 140, geometry.height() - 140 );
76 
77     mainIcon_.addFile( ":/images/hicolor/16x16/glogg.png" );
78     mainIcon_.addFile( ":/images/hicolor/24x24/glogg.png" );
79     mainIcon_.addFile( ":/images/hicolor/32x32/glogg.png" );
80     mainIcon_.addFile( ":/images/hicolor/48x48/glogg.png" );
81 
82     setWindowIcon( mainIcon_ );
83 
84     readSettings();
85 
86     // Connect the signals to the mux (they will be forwarded to the
87     // "current" crawlerwidget
88 
89     // Send actions to the crawlerwidget
90     signalMux_.connect( this, SIGNAL( followSet( bool ) ),
91             SIGNAL( followSet( bool ) ) );
92     signalMux_.connect( this, SIGNAL( optionsChanged() ),
93             SLOT( applyConfiguration() ) );
94     signalMux_.connect( this, SIGNAL( enteringQuickFind() ),
95             SLOT( enteringQuickFind() ) );
96     signalMux_.connect( &quickFindWidget_, SIGNAL( close() ),
97             SLOT( exitingQuickFind() ) );
98 
99     // Actions from the CrawlerWidget
100     signalMux_.connect( SIGNAL( followDisabled() ),
101             this, SLOT( disableFollow() ) );
102     signalMux_.connect( SIGNAL( updateLineNumber( int ) ),
103             this, SLOT( lineNumberHandler( int ) ) );
104 
105     // Register for progress status bar
106     signalMux_.connect( SIGNAL( loadingProgressed( int ) ),
107             this, SLOT( updateLoadingProgress( int ) ) );
108     signalMux_.connect( SIGNAL( loadingFinished( bool ) ),
109             this, SLOT( displayNormalStatus( bool ) ) );
110 
111     // Configure the main tabbed widget
112     mainTabWidget_.setDocumentMode( true );
113     mainTabWidget_.setMovable( true );
114     //mainTabWidget_.setTabShape( QTabWidget::Triangular );
115     mainTabWidget_.setTabsClosable( true );
116 
117     connect( &mainTabWidget_, SIGNAL( tabCloseRequested ( int ) ),
118             this, SLOT( closeTab( int ) ) );
119 
120     // Establish the QuickFindWidget and mux ( to send requests from the
121     // QFWidget to the right window )
122     connect( &quickFindWidget_, SIGNAL( patternConfirmed( const QString&, bool ) ),
123              &quickFindMux_, SLOT( confirmPattern( const QString&, bool ) ) );
124     connect( &quickFindWidget_, SIGNAL( patternUpdated( const QString&, bool ) ),
125              &quickFindMux_, SLOT( setNewPattern( const QString&, bool ) ) );
126     connect( &quickFindWidget_, SIGNAL( cancelSearch() ),
127              &quickFindMux_, SLOT( cancelSearch() ) );
128     connect( &quickFindWidget_, SIGNAL( searchForward() ),
129              &quickFindMux_, SLOT( searchForward() ) );
130     connect( &quickFindWidget_, SIGNAL( searchBackward() ),
131              &quickFindMux_, SLOT( searchBackward() ) );
132     connect( &quickFindWidget_, SIGNAL( searchNext() ),
133              &quickFindMux_, SLOT( searchNext() ) );
134 
135     // QuickFind changes coming from the views
136     connect( &quickFindMux_, SIGNAL( patternChanged( const QString& ) ),
137              this, SLOT( changeQFPattern( const QString& ) ) );
138     connect( &quickFindMux_, SIGNAL( notify( const QFNotification& ) ),
139              &quickFindWidget_, SLOT( notify( const QFNotification& ) ) );
140     connect( &quickFindMux_, SIGNAL( clearNotification() ),
141              &quickFindWidget_, SLOT( clearNotification() ) );
142 
143     // Construct the QuickFind bar
144     quickFindWidget_.hide();
145 
146     QWidget* central_widget = new QWidget();
147     QVBoxLayout* main_layout = new QVBoxLayout();
148     main_layout->addWidget( &mainTabWidget_ );
149     main_layout->addWidget( &quickFindWidget_ );
150     central_widget->setLayout( main_layout );
151 
152     setCentralWidget( central_widget );
153 }
154 
155 void MainWindow::loadInitialFile( QString fileName )
156 {
157     LOG(logDEBUG) << "loadInitialFile";
158 
159     // Is there a file passed as argument?
160     if ( !fileName.isEmpty() )
161         loadFile( fileName );
162     else if ( !previousFile.isEmpty() )
163         loadFile( previousFile );
164 }
165 
166 //
167 // Private functions
168 //
169 
170 // Menu actions
171 void MainWindow::createActions()
172 {
173     Configuration& config = Persistent<Configuration>( "settings" );
174 
175     openAction = new QAction(tr("&Open..."), this);
176     openAction->setShortcut(QKeySequence::Open);
177     openAction->setIcon( QIcon(":/images/open16.png") );
178     openAction->setStatusTip(tr("Open a file"));
179     connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
180 
181     // Recent files
182     for (int i = 0; i < MaxRecentFiles; ++i) {
183         recentFileActions[i] = new QAction(this);
184         recentFileActions[i]->setVisible(false);
185         connect(recentFileActions[i], SIGNAL(triggered()),
186                 this, SLOT(openRecentFile()));
187     }
188 
189     exitAction = new QAction(tr("E&xit"), this);
190     exitAction->setShortcut(tr("Ctrl+Q"));
191     exitAction->setStatusTip(tr("Exit the application"));
192     connect( exitAction, SIGNAL(triggered()), this, SLOT(close()) );
193 
194     copyAction = new QAction(tr("&Copy"), this);
195     copyAction->setShortcut(QKeySequence::Copy);
196     copyAction->setStatusTip(tr("Copy the selection"));
197     connect( copyAction, SIGNAL(triggered()), this, SLOT(copy()) );
198 
199     selectAllAction = new QAction(tr("Select &All"), this);
200     selectAllAction->setShortcut(tr("Ctrl+A"));
201     selectAllAction->setStatusTip(tr("Select all the text"));
202     connect( selectAllAction, SIGNAL(triggered()),
203              this, SLOT( selectAll() ) );
204 
205     findAction = new QAction(tr("&Find..."), this);
206     findAction->setShortcut(QKeySequence::Find);
207     findAction->setStatusTip(tr("Find the text"));
208     connect( findAction, SIGNAL(triggered()),
209             this, SLOT( find() ) );
210 
211     overviewVisibleAction = new QAction( tr("Matches &overview"), this );
212     overviewVisibleAction->setCheckable( true );
213     overviewVisibleAction->setChecked( config.isOverviewVisible() );
214     connect( overviewVisibleAction, SIGNAL( toggled( bool ) ),
215             this, SLOT( toggleOverviewVisibility( bool )) );
216 
217     lineNumbersVisibleInMainAction =
218         new QAction( tr("Line &numbers in main view"), this );
219     lineNumbersVisibleInMainAction->setCheckable( true );
220     lineNumbersVisibleInMainAction->setChecked( config.mainLineNumbersVisible() );
221     connect( lineNumbersVisibleInMainAction, SIGNAL( toggled( bool ) ),
222             this, SLOT( toggleMainLineNumbersVisibility( bool )) );
223 
224     lineNumbersVisibleInFilteredAction =
225         new QAction( tr("Line &numbers in filtered view"), this );
226     lineNumbersVisibleInFilteredAction->setCheckable( true );
227     lineNumbersVisibleInFilteredAction->setChecked( config.filteredLineNumbersVisible() );
228     connect( lineNumbersVisibleInFilteredAction, SIGNAL( toggled( bool ) ),
229             this, SLOT( toggleFilteredLineNumbersVisibility( bool )) );
230 
231     followAction = new QAction( tr("&Follow File"), this );
232     followAction->setShortcut(Qt::Key_F);
233     followAction->setCheckable(true);
234     connect( followAction, SIGNAL(toggled( bool )),
235             this, SIGNAL(followSet( bool )) );
236 
237     reloadAction = new QAction( tr("&Reload"), this );
238     reloadAction->setShortcut(QKeySequence::Refresh);
239     reloadAction->setIcon( QIcon(":/images/reload16.png") );
240     signalMux_.connect( reloadAction, SIGNAL(triggered()), SLOT(reload()) );
241 
242     stopAction = new QAction( tr("&Stop"), this );
243     stopAction->setIcon( QIcon(":/images/stop16.png") );
244     stopAction->setEnabled( false );
245     signalMux_.connect( stopAction, SIGNAL(triggered()), SLOT(stopLoading()) );
246 
247     filtersAction = new QAction(tr("&Filters..."), this);
248     filtersAction->setStatusTip(tr("Show the Filters box"));
249     connect( filtersAction, SIGNAL(triggered()), this, SLOT(filters()) );
250 
251     optionsAction = new QAction(tr("&Options..."), this);
252     optionsAction->setStatusTip(tr("Show the Options box"));
253     connect( optionsAction, SIGNAL(triggered()), this, SLOT(options()) );
254 
255     aboutAction = new QAction(tr("&About"), this);
256     aboutAction->setStatusTip(tr("Show the About box"));
257     connect( aboutAction, SIGNAL(triggered()), this, SLOT(about()) );
258 
259     aboutQtAction = new QAction(tr("About &Qt"), this);
260     aboutAction->setStatusTip(tr("Show the Qt library's About box"));
261     connect( aboutQtAction, SIGNAL(triggered()), this, SLOT(aboutQt()) );
262 }
263 
264 void MainWindow::createMenus()
265 {
266     fileMenu = menuBar()->addMenu( tr("&File") );
267     fileMenu->addAction( openAction );
268     fileMenu->addSeparator();
269     for (int i = 0; i < MaxRecentFiles; ++i) {
270         fileMenu->addAction( recentFileActions[i] );
271         recentFileActionBehaviors[i] =
272             new MenuActionToolTipBehavior(recentFileActions[i], fileMenu, this);
273     }
274     fileMenu->addSeparator();
275     fileMenu->addAction( exitAction );
276 
277     editMenu = menuBar()->addMenu( tr("&Edit") );
278     editMenu->addAction( copyAction );
279     editMenu->addAction( selectAllAction );
280     editMenu->addSeparator();
281     editMenu->addAction( findAction );
282 
283     viewMenu = menuBar()->addMenu( tr("&View") );
284     viewMenu->addAction( overviewVisibleAction );
285     viewMenu->addSeparator();
286     viewMenu->addAction( lineNumbersVisibleInMainAction );
287     viewMenu->addAction( lineNumbersVisibleInFilteredAction );
288     viewMenu->addSeparator();
289     viewMenu->addAction( followAction );
290     viewMenu->addSeparator();
291     viewMenu->addAction( reloadAction );
292 
293     toolsMenu = menuBar()->addMenu( tr("&Tools") );
294     toolsMenu->addAction( filtersAction );
295     toolsMenu->addSeparator();
296     toolsMenu->addAction( optionsAction );
297 
298     menuBar()->addSeparator();
299 
300     helpMenu = menuBar()->addMenu( tr("&Help") );
301     helpMenu->addAction( aboutAction );
302 }
303 
304 void MainWindow::createToolBars()
305 {
306     infoLine = new InfoLine();
307     infoLine->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
308     infoLine->setLineWidth( 0 );
309 
310     lineNbField = new QLabel( );
311     lineNbField->setText( "Line 0" );
312     lineNbField->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
313     lineNbField->setMinimumSize(
314             lineNbField->fontMetrics().size( 0, "Line 0000000") );
315 
316     toolBar = addToolBar( tr("&Toolbar") );
317     toolBar->setIconSize( QSize( 16, 16 ) );
318     toolBar->setMovable( false );
319     toolBar->addAction( openAction );
320     toolBar->addAction( reloadAction );
321     toolBar->addWidget( infoLine );
322     toolBar->addAction( stopAction );
323     toolBar->addWidget( lineNbField );
324 }
325 
326 //
327 // Slots
328 //
329 
330 // Opens the file selection dialog to select a new log file
331 void MainWindow::open()
332 {
333     QString defaultDir = ".";
334 
335     // Default to the path of the current file if there is one
336     if ( !currentFile.isEmpty() ) {
337         QFileInfo fileInfo = QFileInfo( currentFile );
338         defaultDir = fileInfo.path();
339     }
340 
341     QString fileName = QFileDialog::getOpenFileName(this,
342             tr("Open file"), defaultDir, tr("All files (*)"));
343     if (!fileName.isEmpty())
344         loadFile(fileName);
345 }
346 
347 // Opens a log file from the recent files list
348 void MainWindow::openRecentFile()
349 {
350     QAction* action = qobject_cast<QAction*>(sender());
351     if (action)
352         loadFile(action->data().toString());
353 }
354 
355 // Select all the text in the currently selected view
356 void MainWindow::selectAll()
357 {
358     CrawlerWidget* current = currentCrawlerWidget();
359 
360     if ( current )
361         current->selectAll();
362 }
363 
364 // Copy the currently selected line into the clipboard
365 void MainWindow::copy()
366 {
367     static QClipboard* clipboard = QApplication::clipboard();
368     CrawlerWidget* current = currentCrawlerWidget();
369 
370     if ( current ) {
371         clipboard->setText( current->getSelectedText() );
372 
373         // Put it in the global selection as well (X11 only)
374         clipboard->setText( current->getSelectedText(),
375                 QClipboard::Selection );
376     }
377 }
378 
379 // Display the QuickFind bar
380 void MainWindow::find()
381 {
382     displayQuickFindBar( QuickFindMux::Forward );
383 }
384 
385 // Opens the 'Filters' dialog box
386 void MainWindow::filters()
387 {
388     FiltersDialog dialog(this);
389     signalMux_.connect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
390     dialog.exec();
391     signalMux_.disconnect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
392 }
393 
394 // Opens the 'Options' modal dialog box
395 void MainWindow::options()
396 {
397     OptionsDialog dialog(this);
398     signalMux_.connect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
399     dialog.exec();
400     signalMux_.disconnect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
401 }
402 
403 // Opens the 'About' dialog box.
404 void MainWindow::about()
405 {
406     QMessageBox::about(this, tr("About glogg"),
407             tr("<h2>glogg " GLOGG_VERSION "</h2>"
408                 "<p>A fast, advanced log explorer."
409 #ifdef GLOGG_COMMIT
410                 "<p>Built " GLOGG_DATE " from " GLOGG_COMMIT
411 #endif
412                 "<p>Copyright &copy; 2009, 2010, 2011, 2012, 2013 Nicolas Bonnefon and other contributors"
413                 "<p>You may modify and redistribute the program under the terms of the GPL (version 3 or later)." ) );
414 }
415 
416 // Opens the 'About Qt' dialog box.
417 void MainWindow::aboutQt()
418 {
419 }
420 
421 void MainWindow::toggleOverviewVisibility( bool isVisible )
422 {
423     Configuration& config = Persistent<Configuration>( "settings" );
424     config.setOverviewVisible( isVisible );
425     emit optionsChanged();
426 }
427 
428 void MainWindow::toggleMainLineNumbersVisibility( bool isVisible )
429 {
430     Configuration& config = Persistent<Configuration>( "settings" );
431     config.setMainLineNumbersVisible( isVisible );
432     emit optionsChanged();
433 }
434 
435 void MainWindow::toggleFilteredLineNumbersVisibility( bool isVisible )
436 {
437     Configuration& config = Persistent<Configuration>( "settings" );
438     config.setFilteredLineNumbersVisible( isVisible );
439     emit optionsChanged();
440 }
441 
442 void MainWindow::disableFollow()
443 {
444     followAction->setChecked( false );
445 }
446 
447 void MainWindow::lineNumberHandler( int line )
448 {
449     // The line number received is the internal (starts at 0)
450     lineNbField->setText( tr( "Line %1" ).arg( line + 1 ) );
451 }
452 
453 void MainWindow::updateLoadingProgress( int progress )
454 {
455     LOG(logDEBUG) << "Loading progress: " << progress;
456 
457     // We ignore 0% and 100% to avoid a flash when the file (or update)
458     // is very short.
459     if ( progress > 0 && progress < 100 ) {
460         infoLine->setText( loadingFileName + tr( " - Indexing lines... (%1 %)" ).arg( progress ) );
461         infoLine->displayGauge( progress );
462 
463         stopAction->setEnabled( true );
464     }
465 }
466 
467 void MainWindow::displayNormalStatus( bool success )
468 {
469     QLocale defaultLocale;
470 
471     LOG(logDEBUG) << "displayNormalStatus";
472 
473     if ( success )
474         setCurrentFile( loadingFileName );
475 
476     uint64_t fileSize;
477     uint32_t fileNbLine;
478     QDateTime lastModified;
479 
480     session_->getFileInfo( currentCrawlerWidget(),
481             &fileSize, &fileNbLine, &lastModified );
482     if ( lastModified.isValid() ) {
483         const QString date =
484             defaultLocale.toString( lastModified, QLocale::NarrowFormat );
485         infoLine->setText( tr( "%1 (%2 - %3 lines - modified on %4)" )
486                 .arg(currentFile).arg(readableSize(fileSize))
487                 .arg(fileNbLine).arg( date ) );
488     }
489     else {
490         infoLine->setText( tr( "%1 (%2 - %3 lines)" )
491                 .arg(currentFile).arg(readableSize(fileSize))
492                 .arg(fileNbLine) );
493     }
494 
495     infoLine->hideGauge();
496     stopAction->setEnabled( false );
497 
498     // Now everything is ready, we can finally show the file!
499     currentCrawlerWidget()->show();
500     mainTabWidget_.setEnabled( true );
501 }
502 
503 void MainWindow::closeTab( int index )
504 {
505     auto widget = dynamic_cast<CrawlerWidget*>(
506             mainTabWidget_.widget( index ) );
507 
508     assert( widget );
509 
510     mainTabWidget_.removeTab( index );
511     session_->close( widget );
512     delete widget;
513 }
514 
515 void MainWindow::changeQFPattern( const QString& newPattern )
516 {
517     quickFindWidget_.changeDisplayedPattern( newPattern );
518 }
519 
520 //
521 // Events
522 //
523 
524 // Closes the application
525 void MainWindow::closeEvent( QCloseEvent *event )
526 {
527     writeSettings();
528     event->accept();
529 }
530 
531 // Accepts the drag event if it looks like a filename
532 void MainWindow::dragEnterEvent( QDragEnterEvent* event )
533 {
534     if ( event->mimeData()->hasFormat( "text/uri-list" ) )
535         event->acceptProposedAction();
536 }
537 
538 // Tries and loads the file if the URL dropped is local
539 void MainWindow::dropEvent( QDropEvent* event )
540 {
541     QList<QUrl> urls = event->mimeData()->urls();
542     if ( urls.isEmpty() )
543         return;
544 
545     QString fileName = urls.first().toLocalFile();
546     if ( fileName.isEmpty() )
547         return;
548 
549     loadFile( fileName );
550 }
551 
552 void MainWindow::keyPressEvent( QKeyEvent* keyEvent )
553 {
554     LOG(logDEBUG4) << "keyPressEvent received";
555 
556     switch ( (keyEvent->text())[0].toLatin1() ) {
557         case '/':
558             displayQuickFindBar( QuickFindMux::Forward );
559             break;
560         case '?':
561             displayQuickFindBar( QuickFindMux::Backward );
562             break;
563         default:
564             keyEvent->ignore();
565     }
566 
567     if ( !keyEvent->isAccepted() )
568         QMainWindow::keyPressEvent( keyEvent );
569 }
570 
571 //
572 // Private functions
573 //
574 
575 // Create a CrawlerWidget for the passed file, start its loading
576 // and update the title bar.
577 // The loading is done asynchronously.
578 bool MainWindow::loadFile( const QString& fileName )
579 {
580     LOG(logDEBUG) << "loadFile ( " << fileName.toStdString() << " )";
581 
582     // Load the file
583     loadingFileName = fileName;
584 
585     CrawlerWidget* crawler_widget = dynamic_cast<CrawlerWidget*>(
586             session_->open( fileName.toStdString(),
587                 []() { return new CrawlerWidget(); } ) );
588     assert( crawler_widget );
589 
590     // We won't show the widget until the file is fully loaded
591     crawler_widget->hide();
592 
593     // We disable the tab widget to avoid having someone switch
594     // tab during loading. (maybe FIXME)
595     mainTabWidget_.setEnabled( false );
596 
597     int index = mainTabWidget_.addTab( crawler_widget, strippedName( fileName ) );
598 
599     // Setting the new tab, the user will see a blank page for the duration
600     // of the loading, with no way to switch to another tab
601     mainTabWidget_.setCurrentIndex( index );
602 
603     signalMux_.setCurrentDocument( crawler_widget );
604     quickFindMux_.registerSelector( crawler_widget );
605 
606     // FIXME: is it necessary?
607     emit optionsChanged();
608 
609     // We start with the empty file
610     setCurrentFile( "" );
611 
612     LOG(logDEBUG) << "Success loading file " << fileName.toStdString();
613     return true;
614 }
615 
616 // Strips the passed filename from its directory part.
617 QString MainWindow::strippedName( const QString& fullFileName ) const
618 {
619     return QFileInfo( fullFileName ).fileName();
620 }
621 
622 // Return the currently active CrawlerWidget, or NULL if none
623 CrawlerWidget* MainWindow::currentCrawlerWidget() const
624 {
625     auto current = dynamic_cast<CrawlerWidget*>(
626             mainTabWidget_.currentWidget() );
627 
628     return current;
629 }
630 
631 // Add the filename to the recent files list and update the title bar.
632 void MainWindow::setCurrentFile( const QString& fileName )
633 {
634     if ( fileName != currentFile )
635     {
636         // Change the current file
637         currentFile = fileName;
638         QString shownName = tr( "Untitled" );
639         if ( !currentFile.isEmpty() ) {
640             // (reload the list first in case another glogg changed it)
641             GetPersistentInfo().retrieve( "recentFiles" );
642             recentFiles.addRecent( currentFile );
643             GetPersistentInfo().save( "recentFiles" );
644             updateRecentFileActions();
645             shownName = strippedName( currentFile );
646         }
647 
648         setWindowTitle(
649                 tr("%1 - %2").arg(shownName).arg(tr("glogg"))
650 #ifdef GLOGG_COMMIT
651                 + " (dev build " GLOGG_VERSION ")"
652 #endif
653                 );
654     }
655     else
656     {
657         // Nothing, happens when e.g., the file is reloaded
658     }
659 }
660 
661 // Updates the actions for the recent files.
662 // Must be called after having added a new name to the list.
663 void MainWindow::updateRecentFileActions()
664 {
665     QStringList recent_files = recentFiles.recentFiles();
666 
667     for ( int j = 0; j < MaxRecentFiles; ++j ) {
668         if ( j < recent_files.count() ) {
669             QString text = tr("&%1 %2").arg(j + 1).arg(strippedName(recent_files[j]));
670             recentFileActions[j]->setText( text );
671             recentFileActions[j]->setToolTip( recent_files[j] );
672             recentFileActions[j]->setData( recent_files[j] );
673             recentFileActions[j]->setVisible( true );
674         }
675         else {
676             recentFileActions[j]->setVisible( false );
677         }
678     }
679 
680     // separatorAction->setVisible(!recentFiles.isEmpty());
681 }
682 
683 // Write settings to permanent storage
684 void MainWindow::writeSettings()
685 {
686     // Save the session
687     SessionInfo& session = Persistent<SessionInfo>( "session" );
688     session.setGeometry( saveGeometry() );
689     //session.setCrawlerState( crawlerWidget->saveState() );
690     session.setCurrentFile( currentFile );
691     GetPersistentInfo().save( QString( "session" ) );
692 
693     // User settings
694     GetPersistentInfo().save( QString( "settings" ) );
695 }
696 
697 // Read settings from permanent storage
698 void MainWindow::readSettings()
699 {
700     // Get and restore the session
701     GetPersistentInfo().retrieve( QString( "session" ) );
702     SessionInfo session = Persistent<SessionInfo>( "session" );
703     restoreGeometry( session.geometry() );
704     previousFile = session.currentFile();
705     /*
706      * FIXME: should be in the session
707     crawlerWidget->restoreState( session.crawlerState() );
708     */
709 
710     // History of recent files
711     GetPersistentInfo().retrieve( QString( "recentFiles" ) );
712     updateRecentFileActions();
713 
714     GetPersistentInfo().retrieve( QString( "settings" ) );
715     GetPersistentInfo().retrieve( QString( "filterSet" ) );
716 }
717 
718 void MainWindow::displayQuickFindBar( QuickFindMux::QFDirection direction )
719 {
720     LOG(logDEBUG) << "MainWindow::displayQuickFindBar";
721 
722     // Warn crawlers so they can save the position of the focus in order
723     // to do incremental search in the right view.
724     emit enteringQuickFind();
725 
726     quickFindMux_.setDirection( direction );
727     quickFindWidget_.userActivate();
728 }
729 
730 // Returns the size in human readable format
731 static QString readableSize( qint64 size )
732 {
733     static const QString sizeStrs[] = {
734         QObject::tr("B"), QObject::tr("KiB"), QObject::tr("MiB"),
735         QObject::tr("GiB"), QObject::tr("TiB") };
736 
737     QLocale defaultLocale;
738     unsigned int i;
739     double humanSize = size;
740 
741     for ( i=0; i+1 < (sizeof(sizeStrs)/sizeof(QString)) && (humanSize/1024.0) >= 1024.0; i++ )
742         humanSize /= 1024.0;
743 
744     if ( humanSize >= 1024.0 ) {
745         humanSize /= 1024.0;
746         i++;
747     }
748 
749     QString output;
750     if ( i == 0 )
751         // No decimal part if we display straight bytes.
752         output = defaultLocale.toString( (int) humanSize );
753     else
754         output = defaultLocale.toString( humanSize, 'f', 1 );
755 
756     output += QString(" ") + sizeStrs[i];
757 
758     return output;
759 }
760