xref: /glogg/src/mainwindow.cpp (revision 460de7006b8563388072e22dac4e20bfd129dd32)
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 #include "externalcom.h"
53 
54 // Returns the size in human readable format
55 static QString readableSize( qint64 size );
56 
57 MainWindow::MainWindow( std::unique_ptr<Session> session,
58         std::shared_ptr<ExternalCommunicator> external_communicator ) :
59     session_( std::move( session )  ),
60     externalCommunicator_( external_communicator ),
61     recentFiles_( Persistent<RecentFiles>( "recentFiles" ) ),
62     mainIcon_(),
63     signalMux_(),
64     quickFindMux_( session_->getQuickFindPattern() ),
65     mainTabWidget_()
66 #ifdef GLOGG_SUPPORTS_VERSION_CHECKING
67     ,versionChecker_()
68 #endif
69 {
70     createActions();
71     createMenus();
72     createToolBars();
73     // createStatusBar();
74 
75     setAcceptDrops( true );
76 
77     // Default geometry
78     const QRect geometry = QApplication::desktop()->availableGeometry( this );
79     setGeometry( geometry.x() + 20, geometry.y() + 40,
80             geometry.width() - 140, geometry.height() - 140 );
81 
82     mainIcon_.addFile( ":/images/hicolor/16x16/glogg.png" );
83     mainIcon_.addFile( ":/images/hicolor/24x24/glogg.png" );
84     mainIcon_.addFile( ":/images/hicolor/32x32/glogg.png" );
85     mainIcon_.addFile( ":/images/hicolor/48x48/glogg.png" );
86 
87     setWindowIcon( mainIcon_ );
88 
89     readSettings();
90 
91     // Connect the signals to the mux (they will be forwarded to the
92     // "current" crawlerwidget
93 
94     // Send actions to the crawlerwidget
95     signalMux_.connect( this, SIGNAL( followSet( bool ) ),
96             SIGNAL( followSet( bool ) ) );
97     signalMux_.connect( this, SIGNAL( optionsChanged() ),
98             SLOT( applyConfiguration() ) );
99     signalMux_.connect( this, SIGNAL( enteringQuickFind() ),
100             SLOT( enteringQuickFind() ) );
101     signalMux_.connect( &quickFindWidget_, SIGNAL( close() ),
102             SLOT( exitingQuickFind() ) );
103 
104     // Actions from the CrawlerWidget
105     signalMux_.connect( SIGNAL( followDisabled() ),
106             this, SLOT( disableFollow() ) );
107     signalMux_.connect( SIGNAL( updateLineNumber( int ) ),
108             this, SLOT( lineNumberHandler( int ) ) );
109 
110     // Register for progress status bar
111     signalMux_.connect( SIGNAL( loadingProgressed( int ) ),
112             this, SLOT( updateLoadingProgress( int ) ) );
113     signalMux_.connect( SIGNAL( loadingFinished( LoadingStatus ) ),
114             this, SLOT( handleLoadingFinished( LoadingStatus ) ) );
115 
116     // Configure the main tabbed widget
117     mainTabWidget_.setDocumentMode( true );
118     mainTabWidget_.setMovable( true );
119     //mainTabWidget_.setTabShape( QTabWidget::Triangular );
120     mainTabWidget_.setTabsClosable( true );
121 
122     connect( &mainTabWidget_, SIGNAL( tabCloseRequested( int ) ),
123             this, SLOT( closeTab( int ) ) );
124     connect( &mainTabWidget_, SIGNAL( currentChanged( int ) ),
125             this, SLOT( currentTabChanged( int ) ) );
126 
127     // Establish the QuickFindWidget and mux ( to send requests from the
128     // QFWidget to the right window )
129     connect( &quickFindWidget_, SIGNAL( patternConfirmed( const QString&, bool ) ),
130              &quickFindMux_, SLOT( confirmPattern( const QString&, bool ) ) );
131     connect( &quickFindWidget_, SIGNAL( patternUpdated( const QString&, bool ) ),
132              &quickFindMux_, SLOT( setNewPattern( const QString&, bool ) ) );
133     connect( &quickFindWidget_, SIGNAL( cancelSearch() ),
134              &quickFindMux_, SLOT( cancelSearch() ) );
135     connect( &quickFindWidget_, SIGNAL( searchForward() ),
136              &quickFindMux_, SLOT( searchForward() ) );
137     connect( &quickFindWidget_, SIGNAL( searchBackward() ),
138              &quickFindMux_, SLOT( searchBackward() ) );
139     connect( &quickFindWidget_, SIGNAL( searchNext() ),
140              &quickFindMux_, SLOT( searchNext() ) );
141 
142     // QuickFind changes coming from the views
143     connect( &quickFindMux_, SIGNAL( patternChanged( const QString& ) ),
144              this, SLOT( changeQFPattern( const QString& ) ) );
145     connect( &quickFindMux_, SIGNAL( notify( const QFNotification& ) ),
146              &quickFindWidget_, SLOT( notify( const QFNotification& ) ) );
147     connect( &quickFindMux_, SIGNAL( clearNotification() ),
148              &quickFindWidget_, SLOT( clearNotification() ) );
149 
150     // Actions from external instances
151     connect( externalCommunicator_.get(), SIGNAL( loadFile( const QString& ) ),
152              this, SLOT( loadFileNonInteractive( const QString& ) ) );
153 
154     // Version checker notification
155     connect( &versionChecker_, SIGNAL( newVersionFound( const QString& ) ),
156             this, SLOT( newVersionNotification( const QString& ) ) );
157 
158     // Construct the QuickFind bar
159     quickFindWidget_.hide();
160 
161     QWidget* central_widget = new QWidget();
162     QVBoxLayout* main_layout = new QVBoxLayout();
163     main_layout->setContentsMargins( 0, 0, 0, 0 );
164     main_layout->addWidget( &mainTabWidget_ );
165     main_layout->addWidget( &quickFindWidget_ );
166     central_widget->setLayout( main_layout );
167 
168     setCentralWidget( central_widget );
169 }
170 
171 void MainWindow::reloadSession()
172 {
173     int current_file_index = -1;
174 
175     for ( auto open_file: session_->restore(
176                []() { return new CrawlerWidget(); },
177                &current_file_index ) )
178     {
179         QString file_name = { open_file.first.c_str() };
180         CrawlerWidget* crawler_widget = dynamic_cast<CrawlerWidget*>(
181                 open_file.second );
182 
183         assert( crawler_widget );
184 
185         mainTabWidget_.addTab( crawler_widget, strippedName( file_name ) );
186     }
187 
188     if ( current_file_index >= 0 )
189         mainTabWidget_.setCurrentIndex( current_file_index );
190 }
191 
192 void MainWindow::loadInitialFile( QString fileName )
193 {
194     LOG(logDEBUG) << "loadInitialFile";
195 
196     // Is there a file passed as argument?
197     if ( !fileName.isEmpty() )
198         loadFile( fileName );
199 }
200 
201 void MainWindow::startBackgroundTasks()
202 {
203     LOG(logDEBUG) << "startBackgroundTasks";
204 
205 #ifdef GLOGG_SUPPORTS_VERSION_CHECKING
206     versionChecker_.startCheck();
207 #endif
208 }
209 
210 //
211 // Private functions
212 //
213 
214 // Menu actions
215 void MainWindow::createActions()
216 {
217     std::shared_ptr<Configuration> config =
218         Persistent<Configuration>( "settings" );
219 
220     openAction = new QAction(tr("&Open..."), this);
221     openAction->setShortcut(QKeySequence::Open);
222     openAction->setIcon( QIcon(":/images/open16.png") );
223     openAction->setStatusTip(tr("Open a file"));
224     connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
225 
226     // Recent files
227     for (int i = 0; i < MaxRecentFiles; ++i) {
228         recentFileActions[i] = new QAction(this);
229         recentFileActions[i]->setVisible(false);
230         connect(recentFileActions[i], SIGNAL(triggered()),
231                 this, SLOT(openRecentFile()));
232     }
233 
234     exitAction = new QAction(tr("E&xit"), this);
235     exitAction->setShortcut(tr("Ctrl+Q"));
236     exitAction->setStatusTip(tr("Exit the application"));
237     connect( exitAction, SIGNAL(triggered()), this, SLOT(close()) );
238 
239     copyAction = new QAction(tr("&Copy"), this);
240     copyAction->setShortcut(QKeySequence::Copy);
241     copyAction->setStatusTip(tr("Copy the selection"));
242     connect( copyAction, SIGNAL(triggered()), this, SLOT(copy()) );
243 
244     selectAllAction = new QAction(tr("Select &All"), this);
245     selectAllAction->setShortcut(tr("Ctrl+A"));
246     selectAllAction->setStatusTip(tr("Select all the text"));
247     connect( selectAllAction, SIGNAL(triggered()),
248              this, SLOT( selectAll() ) );
249 
250     findAction = new QAction(tr("&Find..."), this);
251     findAction->setShortcut(QKeySequence::Find);
252     findAction->setStatusTip(tr("Find the text"));
253     connect( findAction, SIGNAL(triggered()),
254             this, SLOT( find() ) );
255 
256     overviewVisibleAction = new QAction( tr("Matches &overview"), this );
257     overviewVisibleAction->setCheckable( true );
258     overviewVisibleAction->setChecked( config->isOverviewVisible() );
259     connect( overviewVisibleAction, SIGNAL( toggled( bool ) ),
260             this, SLOT( toggleOverviewVisibility( bool )) );
261 
262     lineNumbersVisibleInMainAction =
263         new QAction( tr("Line &numbers in main view"), this );
264     lineNumbersVisibleInMainAction->setCheckable( true );
265     lineNumbersVisibleInMainAction->setChecked( config->mainLineNumbersVisible() );
266     connect( lineNumbersVisibleInMainAction, SIGNAL( toggled( bool ) ),
267             this, SLOT( toggleMainLineNumbersVisibility( bool )) );
268 
269     lineNumbersVisibleInFilteredAction =
270         new QAction( tr("Line &numbers in filtered view"), this );
271     lineNumbersVisibleInFilteredAction->setCheckable( true );
272     lineNumbersVisibleInFilteredAction->setChecked( config->filteredLineNumbersVisible() );
273     connect( lineNumbersVisibleInFilteredAction, SIGNAL( toggled( bool ) ),
274             this, SLOT( toggleFilteredLineNumbersVisibility( bool )) );
275 
276     followAction = new QAction( tr("&Follow File"), this );
277     followAction->setShortcut(Qt::Key_F);
278     followAction->setCheckable(true);
279     connect( followAction, SIGNAL(toggled( bool )),
280             this, SIGNAL(followSet( bool )) );
281 
282     reloadAction = new QAction( tr("&Reload"), this );
283     reloadAction->setShortcut(QKeySequence::Refresh);
284     reloadAction->setIcon( QIcon(":/images/reload16.png") );
285     signalMux_.connect( reloadAction, SIGNAL(triggered()), SLOT(reload()) );
286 
287     stopAction = new QAction( tr("&Stop"), this );
288     stopAction->setIcon( QIcon(":/images/stop16.png") );
289     stopAction->setEnabled( true );
290     signalMux_.connect( stopAction, SIGNAL(triggered()), SLOT(stopLoading()) );
291 
292     filtersAction = new QAction(tr("&Filters..."), this);
293     filtersAction->setStatusTip(tr("Show the Filters box"));
294     connect( filtersAction, SIGNAL(triggered()), this, SLOT(filters()) );
295 
296     optionsAction = new QAction(tr("&Options..."), this);
297     optionsAction->setStatusTip(tr("Show the Options box"));
298     connect( optionsAction, SIGNAL(triggered()), this, SLOT(options()) );
299 
300     aboutAction = new QAction(tr("&About"), this);
301     aboutAction->setStatusTip(tr("Show the About box"));
302     connect( aboutAction, SIGNAL(triggered()), this, SLOT(about()) );
303 
304     aboutQtAction = new QAction(tr("About &Qt"), this);
305     aboutAction->setStatusTip(tr("Show the Qt library's About box"));
306     connect( aboutQtAction, SIGNAL(triggered()), this, SLOT(aboutQt()) );
307 }
308 
309 void MainWindow::createMenus()
310 {
311     fileMenu = menuBar()->addMenu( tr("&File") );
312     fileMenu->addAction( openAction );
313     fileMenu->addSeparator();
314     for (int i = 0; i < MaxRecentFiles; ++i) {
315         fileMenu->addAction( recentFileActions[i] );
316         recentFileActionBehaviors[i] =
317             new MenuActionToolTipBehavior(recentFileActions[i], fileMenu, this);
318     }
319     fileMenu->addSeparator();
320     fileMenu->addAction( exitAction );
321 
322     editMenu = menuBar()->addMenu( tr("&Edit") );
323     editMenu->addAction( copyAction );
324     editMenu->addAction( selectAllAction );
325     editMenu->addSeparator();
326     editMenu->addAction( findAction );
327 
328     viewMenu = menuBar()->addMenu( tr("&View") );
329     viewMenu->addAction( overviewVisibleAction );
330     viewMenu->addSeparator();
331     viewMenu->addAction( lineNumbersVisibleInMainAction );
332     viewMenu->addAction( lineNumbersVisibleInFilteredAction );
333     viewMenu->addSeparator();
334     viewMenu->addAction( followAction );
335     viewMenu->addSeparator();
336     viewMenu->addAction( reloadAction );
337 
338     toolsMenu = menuBar()->addMenu( tr("&Tools") );
339     toolsMenu->addAction( filtersAction );
340     toolsMenu->addSeparator();
341     toolsMenu->addAction( optionsAction );
342 
343     menuBar()->addSeparator();
344 
345     helpMenu = menuBar()->addMenu( tr("&Help") );
346     helpMenu->addAction( aboutAction );
347 }
348 
349 void MainWindow::createToolBars()
350 {
351     infoLine = new InfoLine();
352     infoLine->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
353     infoLine->setLineWidth( 0 );
354 
355     lineNbField = new QLabel( );
356     lineNbField->setText( "Line 0" );
357     lineNbField->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
358     lineNbField->setMinimumSize(
359             lineNbField->fontMetrics().size( 0, "Line 0000000") );
360 
361     toolBar = addToolBar( tr("&Toolbar") );
362     toolBar->setIconSize( QSize( 16, 16 ) );
363     toolBar->setMovable( false );
364     toolBar->addAction( openAction );
365     toolBar->addAction( reloadAction );
366     toolBar->addWidget( infoLine );
367     toolBar->addAction( stopAction );
368     toolBar->addWidget( lineNbField );
369 }
370 
371 //
372 // Slots
373 //
374 
375 // Opens the file selection dialog to select a new log file
376 void MainWindow::open()
377 {
378     QString defaultDir = ".";
379 
380     // Default to the path of the current file if there is one
381     if ( auto current = currentCrawlerWidget() )
382     {
383         std::string current_file = session_->getFilename( current );
384         QFileInfo fileInfo = QFileInfo( QString( current_file.c_str() ) );
385         defaultDir = fileInfo.path();
386     }
387 
388     QString fileName = QFileDialog::getOpenFileName(this,
389             tr("Open file"), defaultDir, tr("All files (*)"));
390     if (!fileName.isEmpty())
391         loadFile(fileName);
392 }
393 
394 // Opens a log file from the recent files list
395 void MainWindow::openRecentFile()
396 {
397     QAction* action = qobject_cast<QAction*>(sender());
398     if (action)
399         loadFile(action->data().toString());
400 }
401 
402 // Select all the text in the currently selected view
403 void MainWindow::selectAll()
404 {
405     CrawlerWidget* current = currentCrawlerWidget();
406 
407     if ( current )
408         current->selectAll();
409 }
410 
411 // Copy the currently selected line into the clipboard
412 void MainWindow::copy()
413 {
414     static QClipboard* clipboard = QApplication::clipboard();
415     CrawlerWidget* current = currentCrawlerWidget();
416 
417     if ( current ) {
418         clipboard->setText( current->getSelectedText() );
419 
420         // Put it in the global selection as well (X11 only)
421         clipboard->setText( current->getSelectedText(),
422                 QClipboard::Selection );
423     }
424 }
425 
426 // Display the QuickFind bar
427 void MainWindow::find()
428 {
429     displayQuickFindBar( QuickFindMux::Forward );
430 }
431 
432 // Opens the 'Filters' dialog box
433 void MainWindow::filters()
434 {
435     FiltersDialog dialog(this);
436     signalMux_.connect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
437     dialog.exec();
438     signalMux_.disconnect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
439 }
440 
441 // Opens the 'Options' modal dialog box
442 void MainWindow::options()
443 {
444     OptionsDialog dialog(this);
445     signalMux_.connect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
446     dialog.exec();
447     signalMux_.disconnect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
448 }
449 
450 // Opens the 'About' dialog box.
451 void MainWindow::about()
452 {
453     QMessageBox::about(this, tr("About glogg"),
454             tr("<h2>glogg " GLOGG_VERSION "</h2>"
455                 "<p>A fast, advanced log explorer."
456 #ifdef GLOGG_COMMIT
457                 "<p>Built " GLOGG_DATE " from " GLOGG_COMMIT
458 #endif
459                 "<p><a href=\"http://glogg.bonnefon.org/\">http://glogg.bonnefon.org/</a></p>"
460                 "<p>Copyright &copy; 2009, 2010, 2011, 2012, 2013, 2014 Nicolas Bonnefon and other contributors"
461                 "<p>You may modify and redistribute the program under the terms of the GPL (version 3 or later)." ) );
462 }
463 
464 // Opens the 'About Qt' dialog box.
465 void MainWindow::aboutQt()
466 {
467 }
468 
469 void MainWindow::toggleOverviewVisibility( bool isVisible )
470 {
471     std::shared_ptr<Configuration> config =
472         Persistent<Configuration>( "settings" );
473     config->setOverviewVisible( isVisible );
474     emit optionsChanged();
475 }
476 
477 void MainWindow::toggleMainLineNumbersVisibility( bool isVisible )
478 {
479     std::shared_ptr<Configuration> config =
480         Persistent<Configuration>( "settings" );
481     config->setMainLineNumbersVisible( isVisible );
482     emit optionsChanged();
483 }
484 
485 void MainWindow::toggleFilteredLineNumbersVisibility( bool isVisible )
486 {
487     std::shared_ptr<Configuration> config =
488         Persistent<Configuration>( "settings" );
489     config->setFilteredLineNumbersVisible( isVisible );
490     emit optionsChanged();
491 }
492 
493 void MainWindow::disableFollow()
494 {
495     followAction->setChecked( false );
496 }
497 
498 void MainWindow::lineNumberHandler( int line )
499 {
500     // The line number received is the internal (starts at 0)
501     lineNbField->setText( tr( "Line %1" ).arg( line + 1 ) );
502 }
503 
504 void MainWindow::updateLoadingProgress( int progress )
505 {
506     LOG(logDEBUG) << "Loading progress: " << progress;
507 
508     QString current_file =
509         session_->getFilename( currentCrawlerWidget() ).c_str();
510 
511     // We ignore 0% and 100% to avoid a flash when the file (or update)
512     // is very short.
513     if ( progress > 0 && progress < 100 ) {
514         infoLine->setText( current_file +
515                 tr( " - Indexing lines... (%1 %)" ).arg( progress ) );
516         infoLine->displayGauge( progress );
517 
518         stopAction->setEnabled( true );
519         reloadAction->setEnabled( false );
520     }
521 }
522 
523 void MainWindow::handleLoadingFinished( LoadingStatus status )
524 {
525     QLocale defaultLocale;
526 
527     LOG(logDEBUG) << "handleLoadingFinished success=" <<
528         ( status == LoadingStatus::Successful );
529 
530     // No file is loading
531     loadingFileName.clear();
532 
533     if ( status == LoadingStatus::Successful )
534     {
535         // Following should always work as we will only receive enter
536         // this slot if there is a crawler connected.
537         QString current_file =
538             session_->getFilename( currentCrawlerWidget() ).c_str();
539 
540         uint64_t fileSize;
541         uint32_t fileNbLine;
542         QDateTime lastModified;
543 
544         session_->getFileInfo( currentCrawlerWidget(),
545                 &fileSize, &fileNbLine, &lastModified );
546         if ( lastModified.isValid() ) {
547             const QString date =
548                 defaultLocale.toString( lastModified, QLocale::NarrowFormat );
549             infoLine->setText( tr( "%1 (%2 - %3 lines - modified on %4)" )
550                     .arg(current_file).arg(readableSize(fileSize))
551                     .arg(fileNbLine).arg( date ) );
552         }
553         else {
554             infoLine->setText( tr( "%1 (%2 - %3 lines)" )
555                     .arg(current_file).arg(readableSize(fileSize))
556                     .arg(fileNbLine) );
557         }
558 
559         infoLine->hideGauge();
560         stopAction->setEnabled( false );
561         reloadAction->setEnabled( true );
562 
563         // Now everything is ready, we can finally show the file!
564         currentCrawlerWidget()->show();
565     }
566     else
567     {
568         if ( status == LoadingStatus::NoMemory )
569         {
570             QMessageBox alertBox;
571             alertBox.setText( "Not enough memory." );
572             alertBox.setInformativeText( "The system does not have enough \
573 memory to hold the index for this file. The file will now be closed." );
574             alertBox.setIcon( QMessageBox::Critical );
575             alertBox.exec();
576         }
577 
578         closeTab( mainTabWidget_.currentIndex()  );
579     }
580 
581     // mainTabWidget_.setEnabled( true );
582 }
583 
584 void MainWindow::closeTab( int index )
585 {
586     auto widget = dynamic_cast<CrawlerWidget*>(
587             mainTabWidget_.widget( index ) );
588 
589     assert( widget );
590 
591     widget->stopLoading();
592     mainTabWidget_.removeTab( index );
593     session_->close( widget );
594     delete widget;
595 }
596 
597 void MainWindow::currentTabChanged( int index )
598 {
599     LOG(logDEBUG) << "currentTabChanged";
600 
601     if ( index >= 0 )
602     {
603         CrawlerWidget* crawler_widget = dynamic_cast<CrawlerWidget*>(
604                 mainTabWidget_.widget( index ) );
605         signalMux_.setCurrentDocument( crawler_widget );
606         quickFindMux_.registerSelector( crawler_widget );
607 
608         // New tab is set up with fonts etc...
609         emit optionsChanged();
610 
611         // Update the title bar
612         updateTitleBar( QString(
613                     session_->getFilename( crawler_widget ).c_str() ) );
614     }
615     else
616     {
617         // No tab left
618         signalMux_.setCurrentDocument( nullptr );
619         quickFindMux_.registerSelector( nullptr );
620 
621         infoLine->hideGauge();
622         infoLine->clear();
623 
624         updateTitleBar( QString() );
625     }
626 }
627 
628 void MainWindow::changeQFPattern( const QString& newPattern )
629 {
630     quickFindWidget_.changeDisplayedPattern( newPattern );
631 }
632 
633 void MainWindow::loadFileNonInteractive( const QString& file_name )
634 {
635     LOG(logDEBUG) << "loadFileNonInteractive( "
636         << file_name.toStdString() << " )";
637 
638     loadFile( file_name );
639 }
640 
641 void MainWindow::newVersionNotification( const QString& new_version )
642 {
643     LOG(logDEBUG) << "newVersionNotification( " <<
644         new_version.toStdString() << " )";
645 
646     QMessageBox msgBox;
647     msgBox.setText( QString( "A new version of glogg (%1) is available for download <p>"
648                 "<a href=\"http://glogg.bonnefon.org/download.html\">http://glogg.bonnefon.org/download.html</a>"
649                 ).arg( new_version ) );
650     msgBox.exec();
651 }
652 
653 //
654 // Events
655 //
656 
657 // Closes the application
658 void MainWindow::closeEvent( QCloseEvent *event )
659 {
660     writeSettings();
661     event->accept();
662 }
663 
664 // Accepts the drag event if it looks like a filename
665 void MainWindow::dragEnterEvent( QDragEnterEvent* event )
666 {
667     if ( event->mimeData()->hasFormat( "text/uri-list" ) )
668         event->acceptProposedAction();
669 }
670 
671 // Tries and loads the file if the URL dropped is local
672 void MainWindow::dropEvent( QDropEvent* event )
673 {
674     QList<QUrl> urls = event->mimeData()->urls();
675     if ( urls.isEmpty() )
676         return;
677 
678     QString fileName = urls.first().toLocalFile();
679     if ( fileName.isEmpty() )
680         return;
681 
682     loadFile( fileName );
683 }
684 
685 void MainWindow::keyPressEvent( QKeyEvent* keyEvent )
686 {
687     LOG(logDEBUG4) << "keyPressEvent received";
688 
689     switch ( (keyEvent->text())[0].toLatin1() ) {
690         case '/':
691             displayQuickFindBar( QuickFindMux::Forward );
692             break;
693         case '?':
694             displayQuickFindBar( QuickFindMux::Backward );
695             break;
696         default:
697             keyEvent->ignore();
698     }
699 
700     if ( !keyEvent->isAccepted() )
701         QMainWindow::keyPressEvent( keyEvent );
702 }
703 
704 //
705 // Private functions
706 //
707 
708 // Create a CrawlerWidget for the passed file, start its loading
709 // and update the title bar.
710 // The loading is done asynchronously.
711 bool MainWindow::loadFile( const QString& fileName )
712 {
713     LOG(logDEBUG) << "loadFile ( " << fileName.toStdString() << " )";
714 
715     // First check if the file is already open...
716     CrawlerWidget* existing_crawler = dynamic_cast<CrawlerWidget*>(
717             session_->getViewIfOpen( fileName.toStdString() ) );
718     if ( existing_crawler ) {
719         // ... and switch to it.
720         mainTabWidget_.setCurrentWidget( existing_crawler );
721 
722         return true;
723     }
724 
725     // Load the file
726     loadingFileName = fileName;
727 
728     try {
729         CrawlerWidget* crawler_widget = dynamic_cast<CrawlerWidget*>(
730                 session_->open( fileName.toStdString(),
731                     []() { return new CrawlerWidget(); } ) );
732         assert( crawler_widget );
733 
734         // We won't show the widget until the file is fully loaded
735         crawler_widget->hide();
736 
737         // We disable the tab widget to avoid having someone switch
738         // tab during loading. (maybe FIXME)
739         //mainTabWidget_.setEnabled( false );
740 
741         int index = mainTabWidget_.addTab(
742                 crawler_widget, strippedName( fileName ) );
743 
744         // Setting the new tab, the user will see a blank page for the duration
745         // of the loading, with no way to switch to another tab
746         mainTabWidget_.setCurrentIndex( index );
747 
748         // Update the recent files list
749         // (reload the list first in case another glogg changed it)
750         GetPersistentInfo().retrieve( "recentFiles" );
751         recentFiles_->addRecent( fileName );
752         GetPersistentInfo().save( "recentFiles" );
753         updateRecentFileActions();
754     }
755     catch ( FileUnreadableErr ) {
756         LOG(logDEBUG) << "Can't open file " << fileName.toStdString();
757         return false;
758     }
759 
760     LOG(logDEBUG) << "Success loading file " << fileName.toStdString();
761     return true;
762 
763 }
764 
765 // Strips the passed filename from its directory part.
766 QString MainWindow::strippedName( const QString& fullFileName ) const
767 {
768     return QFileInfo( fullFileName ).fileName();
769 }
770 
771 // Return the currently active CrawlerWidget, or NULL if none
772 CrawlerWidget* MainWindow::currentCrawlerWidget() const
773 {
774     auto current = dynamic_cast<CrawlerWidget*>(
775             mainTabWidget_.currentWidget() );
776 
777     return current;
778 }
779 
780 // Update the title bar.
781 void MainWindow::updateTitleBar( const QString& file_name )
782 {
783     QString shownName = tr( "Untitled" );
784     if ( !file_name.isEmpty() )
785         shownName = strippedName( file_name );
786 
787     setWindowTitle(
788             tr("%1 - %2").arg(shownName).arg(tr("glogg"))
789 #ifdef GLOGG_COMMIT
790             + " (dev build " GLOGG_VERSION ")"
791 #endif
792             );
793 }
794 
795 // Updates the actions for the recent files.
796 // Must be called after having added a new name to the list.
797 void MainWindow::updateRecentFileActions()
798 {
799     QStringList recent_files = recentFiles_->recentFiles();
800 
801     for ( int j = 0; j < MaxRecentFiles; ++j ) {
802         if ( j < recent_files.count() ) {
803             QString text = tr("&%1 %2").arg(j + 1).arg(strippedName(recent_files[j]));
804             recentFileActions[j]->setText( text );
805             recentFileActions[j]->setToolTip( recent_files[j] );
806             recentFileActions[j]->setData( recent_files[j] );
807             recentFileActions[j]->setVisible( true );
808         }
809         else {
810             recentFileActions[j]->setVisible( false );
811         }
812     }
813 
814     // separatorAction->setVisible(!recentFiles.isEmpty());
815 }
816 
817 // Write settings to permanent storage
818 void MainWindow::writeSettings()
819 {
820     // Save the session
821     // Generate the ordered list of widgets and their topLine
822     std::vector<
823             std::tuple<const ViewInterface*, uint64_t, std::shared_ptr<const ViewContextInterface>>
824         > widget_list;
825     for ( int i = 0; i < mainTabWidget_.count(); ++i )
826     {
827         auto view = dynamic_cast<const ViewInterface*>( mainTabWidget_.widget( i ) );
828         widget_list.push_back( std::make_tuple(
829                 view,
830                 0UL,
831                 view->context() ) );
832     }
833     session_->save( widget_list );
834     //SessionInfo& session = Persistent<SessionInfo>( "session" );
835     //session.setGeometry( saveGeometry() );
836     //session.setCrawlerState( crawlerWidget->saveState() );
837     //GetPersistentInfo().save( QString( "session" ) );
838 
839     // User settings
840     GetPersistentInfo().save( QString( "settings" ) );
841 }
842 
843 // Read settings from permanent storage
844 void MainWindow::readSettings()
845 {
846     // Get and restore the session
847     // GetPersistentInfo().retrieve( QString( "session" ) );
848     // SessionInfo session = Persistent<SessionInfo>( "session" );
849     //restoreGeometry( session.geometry() );
850     /*
851      * FIXME: should be in the session
852     crawlerWidget->restoreState( session.crawlerState() );
853     */
854 
855     // History of recent files
856     GetPersistentInfo().retrieve( QString( "recentFiles" ) );
857     updateRecentFileActions();
858 
859     // GetPersistentInfo().retrieve( QString( "settings" ) );
860     GetPersistentInfo().retrieve( QString( "filterSet" ) );
861 }
862 
863 void MainWindow::displayQuickFindBar( QuickFindMux::QFDirection direction )
864 {
865     LOG(logDEBUG) << "MainWindow::displayQuickFindBar";
866 
867     // Warn crawlers so they can save the position of the focus in order
868     // to do incremental search in the right view.
869     emit enteringQuickFind();
870 
871     quickFindMux_.setDirection( direction );
872     quickFindWidget_.userActivate();
873 }
874 
875 // Returns the size in human readable format
876 static QString readableSize( qint64 size )
877 {
878     static const QString sizeStrs[] = {
879         QObject::tr("B"), QObject::tr("KiB"), QObject::tr("MiB"),
880         QObject::tr("GiB"), QObject::tr("TiB") };
881 
882     QLocale defaultLocale;
883     unsigned int i;
884     double humanSize = size;
885 
886     for ( i=0; i+1 < (sizeof(sizeStrs)/sizeof(QString)) && (humanSize/1024.0) >= 1024.0; i++ )
887         humanSize /= 1024.0;
888 
889     if ( humanSize >= 1024.0 ) {
890         humanSize /= 1024.0;
891         i++;
892     }
893 
894     QString output;
895     if ( i == 0 )
896         // No decimal part if we display straight bytes.
897         output = defaultLocale.toString( (int) humanSize );
898     else
899         output = defaultLocale.toString( humanSize, 'f', 1 );
900 
901     output += QString(" ") + sizeStrs[i];
902 
903     return output;
904 }
905