xref: /glogg/src/mainwindow.cpp (revision 048334c92fb4b86ebbabc7471f7313a1cc515c10)
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( followModeChanged( bool ) ),
106             this, SLOT( changeFollowMode( bool ) ) );
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     // Register for checkbox changes
117     signalMux_.connect( SIGNAL( searchRefreshChanged( int ) ),
118             this, SLOT( handleSearchRefreshChanged( int ) ) );
119     signalMux_.connect( SIGNAL( ignoreCaseChanged( int ) ),
120             this, SLOT( handleIgnoreCaseChanged( int ) ) );
121 
122     // Configure the main tabbed widget
123     mainTabWidget_.setDocumentMode( true );
124     mainTabWidget_.setMovable( true );
125     //mainTabWidget_.setTabShape( QTabWidget::Triangular );
126     mainTabWidget_.setTabsClosable( true );
127 
128     connect( &mainTabWidget_, SIGNAL( tabCloseRequested( int ) ),
129             this, SLOT( closeTab( int ) ) );
130     connect( &mainTabWidget_, SIGNAL( currentChanged( int ) ),
131             this, SLOT( currentTabChanged( int ) ) );
132 
133     // Establish the QuickFindWidget and mux ( to send requests from the
134     // QFWidget to the right window )
135     connect( &quickFindWidget_, SIGNAL( patternConfirmed( const QString&, bool ) ),
136              &quickFindMux_, SLOT( confirmPattern( const QString&, bool ) ) );
137     connect( &quickFindWidget_, SIGNAL( patternUpdated( const QString&, bool ) ),
138              &quickFindMux_, SLOT( setNewPattern( const QString&, bool ) ) );
139     connect( &quickFindWidget_, SIGNAL( cancelSearch() ),
140              &quickFindMux_, SLOT( cancelSearch() ) );
141     connect( &quickFindWidget_, SIGNAL( searchForward() ),
142              &quickFindMux_, SLOT( searchForward() ) );
143     connect( &quickFindWidget_, SIGNAL( searchBackward() ),
144              &quickFindMux_, SLOT( searchBackward() ) );
145     connect( &quickFindWidget_, SIGNAL( searchNext() ),
146              &quickFindMux_, SLOT( searchNext() ) );
147 
148     // QuickFind changes coming from the views
149     connect( &quickFindMux_, SIGNAL( patternChanged( const QString& ) ),
150              this, SLOT( changeQFPattern( const QString& ) ) );
151     connect( &quickFindMux_, SIGNAL( notify( const QFNotification& ) ),
152              &quickFindWidget_, SLOT( notify( const QFNotification& ) ) );
153     connect( &quickFindMux_, SIGNAL( clearNotification() ),
154              &quickFindWidget_, SLOT( clearNotification() ) );
155 
156     // Actions from external instances
157     connect( externalCommunicator_.get(), SIGNAL( loadFile( const QString& ) ),
158              this, SLOT( loadFileNonInteractive( const QString& ) ) );
159     connect( qApp, SIGNAL( loadFile( const QString& ) ),
160              this, SLOT( loadFileNonInteractive( const QString& ) ) );
161 
162 #ifdef GLOGG_SUPPORTS_VERSION_CHECKING
163     // Version checker notification
164     connect( &versionChecker_, SIGNAL( newVersionFound( const QString& ) ),
165             this, SLOT( newVersionNotification( const QString& ) ) );
166 #endif
167 
168     // Construct the QuickFind bar
169     quickFindWidget_.hide();
170 
171     QWidget* central_widget = new QWidget();
172     QVBoxLayout* main_layout = new QVBoxLayout();
173     main_layout->setContentsMargins( 0, 0, 0, 0 );
174     main_layout->addWidget( &mainTabWidget_ );
175     main_layout->addWidget( &quickFindWidget_ );
176     central_widget->setLayout( main_layout );
177 
178     setCentralWidget( central_widget );
179 }
180 
181 void MainWindow::reloadGeometry()
182 {
183     QByteArray geometry;
184 
185     session_->storedGeometry( &geometry );
186     restoreGeometry( geometry );
187 }
188 
189 void MainWindow::reloadSession()
190 {
191     int current_file_index = -1;
192 
193     for ( auto open_file: session_->restore(
194                []() { return new CrawlerWidget(); },
195                &current_file_index ) )
196     {
197         QString file_name = { open_file.first.c_str() };
198         CrawlerWidget* crawler_widget = dynamic_cast<CrawlerWidget*>(
199                 open_file.second );
200 
201         assert( crawler_widget );
202 
203         mainTabWidget_.addTab( crawler_widget, strippedName( file_name ) );
204     }
205 
206     if ( current_file_index >= 0 )
207         mainTabWidget_.setCurrentIndex( current_file_index );
208 }
209 
210 void MainWindow::loadInitialFile( QString fileName )
211 {
212     LOG(logDEBUG) << "loadInitialFile";
213 
214     // Is there a file passed as argument?
215     if ( !fileName.isEmpty() )
216         loadFile( fileName );
217 }
218 
219 void MainWindow::startBackgroundTasks()
220 {
221     LOG(logDEBUG) << "startBackgroundTasks";
222 
223 #ifdef GLOGG_SUPPORTS_VERSION_CHECKING
224     versionChecker_.startCheck();
225 #endif
226 }
227 
228 //
229 // Private functions
230 //
231 
232 const MainWindow::EncodingList MainWindow::encoding_list[] = {
233     { "&Auto" },
234     { "ASCII / &ISO-8859-1" },
235     { "&UTF-8" },
236     { "UTF-16LE" },
237     { "UTF-16BE" },
238     { "CP1251" },
239     { "CP1252" },
240     { "&Big5" },
241     { "&GB18030 / GB2312" },
242     { "&Shift_JIS" },
243     { "&KOI8-R" }
244 };
245 
246 // Menu actions
247 void MainWindow::createActions()
248 {
249     std::shared_ptr<Configuration> config =
250         Persistent<Configuration>( "settings" );
251 
252     openAction = new QAction(tr("&Open..."), this);
253     openAction->setShortcut(QKeySequence::Open);
254     openAction->setIcon( QIcon( ":/images/open14.png" ) );
255     openAction->setStatusTip(tr("Open a file"));
256     connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
257 
258     closeAction = new QAction(tr("&Close"), this);
259     closeAction->setShortcut(tr("Ctrl+W"));
260     closeAction->setStatusTip(tr("Close document"));
261     connect(closeAction, SIGNAL(triggered()), this, SLOT(closeTab()));
262 
263     closeAllAction = new QAction(tr("Close &All"), this);
264     closeAllAction->setStatusTip(tr("Close all documents"));
265     connect(closeAllAction, SIGNAL(triggered()), this, SLOT(closeAll()));
266 
267     // Recent files
268     for (int i = 0; i < MaxRecentFiles; ++i) {
269         recentFileActions[i] = new QAction(this);
270         recentFileActions[i]->setVisible(false);
271         connect(recentFileActions[i], SIGNAL(triggered()),
272                 this, SLOT(openRecentFile()));
273     }
274 
275     exitAction = new QAction(tr("E&xit"), this);
276     exitAction->setShortcut(tr("Ctrl+Q"));
277     exitAction->setStatusTip(tr("Exit the application"));
278     connect( exitAction, SIGNAL(triggered()), this, SLOT(close()) );
279 
280     copyAction = new QAction(tr("&Copy"), this);
281     copyAction->setShortcut(QKeySequence::Copy);
282     copyAction->setStatusTip(tr("Copy the selection"));
283     connect( copyAction, SIGNAL(triggered()), this, SLOT(copy()) );
284 
285     selectAllAction = new QAction(tr("Select &All"), this);
286     selectAllAction->setShortcut(tr("Ctrl+A"));
287     selectAllAction->setStatusTip(tr("Select all the text"));
288     connect( selectAllAction, SIGNAL(triggered()),
289              this, SLOT( selectAll() ) );
290 
291     findAction = new QAction(tr("&Find..."), this);
292     findAction->setShortcut(QKeySequence::Find);
293     findAction->setStatusTip(tr("Find the text"));
294     connect( findAction, SIGNAL(triggered()),
295             this, SLOT( find() ) );
296 
297     overviewVisibleAction = new QAction( tr("Matches &overview"), this );
298     overviewVisibleAction->setCheckable( true );
299     overviewVisibleAction->setChecked( config->isOverviewVisible() );
300     connect( overviewVisibleAction, SIGNAL( toggled( bool ) ),
301             this, SLOT( toggleOverviewVisibility( bool )) );
302 
303     lineNumbersVisibleInMainAction =
304         new QAction( tr("Line &numbers in main view"), this );
305     lineNumbersVisibleInMainAction->setCheckable( true );
306     lineNumbersVisibleInMainAction->setChecked( config->mainLineNumbersVisible() );
307     connect( lineNumbersVisibleInMainAction, SIGNAL( toggled( bool ) ),
308             this, SLOT( toggleMainLineNumbersVisibility( bool )) );
309 
310     lineNumbersVisibleInFilteredAction =
311         new QAction( tr("Line &numbers in filtered view"), this );
312     lineNumbersVisibleInFilteredAction->setCheckable( true );
313     lineNumbersVisibleInFilteredAction->setChecked( config->filteredLineNumbersVisible() );
314     connect( lineNumbersVisibleInFilteredAction, SIGNAL( toggled( bool ) ),
315             this, SLOT( toggleFilteredLineNumbersVisibility( bool )) );
316 
317     followAction = new QAction( tr("&Follow File"), this );
318     followAction->setShortcut(Qt::Key_F);
319     followAction->setCheckable(true);
320     connect( followAction, SIGNAL(toggled( bool )),
321             this, SIGNAL(followSet( bool )) );
322 
323     reloadAction = new QAction( tr("&Reload"), this );
324     reloadAction->setShortcut(QKeySequence::Refresh);
325     reloadAction->setIcon( QIcon(":/images/reload14.png") );
326     signalMux_.connect( reloadAction, SIGNAL(triggered()), SLOT(reload()) );
327 
328     stopAction = new QAction( tr("&Stop"), this );
329     stopAction->setIcon( QIcon(":/images/stop14.png") );
330     stopAction->setEnabled( true );
331     signalMux_.connect( stopAction, SIGNAL(triggered()), SLOT(stopLoading()) );
332 
333     filtersAction = new QAction(tr("&Filters..."), this);
334     filtersAction->setStatusTip(tr("Show the Filters box"));
335     connect( filtersAction, SIGNAL(triggered()), this, SLOT(filters()) );
336 
337     optionsAction = new QAction(tr("&Options..."), this);
338     optionsAction->setStatusTip(tr("Show the Options box"));
339     connect( optionsAction, SIGNAL(triggered()), this, SLOT(options()) );
340 
341     aboutAction = new QAction(tr("&About"), this);
342     aboutAction->setStatusTip(tr("Show the About box"));
343     connect( aboutAction, SIGNAL(triggered()), this, SLOT(about()) );
344 
345     aboutQtAction = new QAction(tr("About &Qt"), this);
346     aboutQtAction->setStatusTip(tr("Show the Qt library's About box"));
347     connect( aboutQtAction, SIGNAL(triggered()), this, SLOT(aboutQt()) );
348 
349     encodingGroup = new QActionGroup( this );
350 
351     for ( int i = 0; i < static_cast<int>( Encoding::ENCODING_MAX ); ++i ) {
352         encodingAction[i] = new QAction( tr( encoding_list[i].name ), this );
353         encodingAction[i]->setCheckable( true );
354         encodingGroup->addAction( encodingAction[i] );
355     }
356 
357     encodingAction[0]->setStatusTip(tr("Automatically detect the file's encoding"));
358     encodingAction[0]->setChecked( true );
359 
360     connect( encodingGroup, SIGNAL( triggered( QAction* ) ),
361             this, SLOT( encodingChanged( QAction* ) ) );
362 }
363 
364 void MainWindow::createMenus()
365 {
366     fileMenu = menuBar()->addMenu( tr("&File") );
367     fileMenu->addAction( openAction );
368     fileMenu->addAction( closeAction );
369     fileMenu->addAction( closeAllAction );
370     fileMenu->addSeparator();
371     for (int i = 0; i < MaxRecentFiles; ++i) {
372         fileMenu->addAction( recentFileActions[i] );
373         recentFileActionBehaviors[i] =
374             new MenuActionToolTipBehavior(recentFileActions[i], fileMenu, this);
375     }
376     fileMenu->addSeparator();
377     fileMenu->addAction( exitAction );
378 
379     editMenu = menuBar()->addMenu( tr("&Edit") );
380     editMenu->addAction( copyAction );
381     editMenu->addAction( selectAllAction );
382     editMenu->addSeparator();
383     editMenu->addAction( findAction );
384 
385     viewMenu = menuBar()->addMenu( tr("&View") );
386     viewMenu->addAction( overviewVisibleAction );
387     viewMenu->addSeparator();
388     viewMenu->addAction( lineNumbersVisibleInMainAction );
389     viewMenu->addAction( lineNumbersVisibleInFilteredAction );
390     viewMenu->addSeparator();
391     viewMenu->addAction( followAction );
392     viewMenu->addSeparator();
393     viewMenu->addAction( reloadAction );
394 
395     toolsMenu = menuBar()->addMenu( tr("&Tools") );
396     toolsMenu->addAction( filtersAction );
397     toolsMenu->addSeparator();
398     toolsMenu->addAction( optionsAction );
399 
400     encodingMenu = menuBar()->addMenu( tr("En&coding") );
401     encodingMenu->addAction( encodingAction[0] );
402     encodingMenu->addSeparator();
403     for ( int i = 1; i < static_cast<int>( Encoding::ENCODING_MAX ); ++i ) {
404         encodingMenu->addAction( encodingAction[i] );
405     }
406 
407     menuBar()->addSeparator();
408 
409     helpMenu = menuBar()->addMenu( tr("&Help") );
410     helpMenu->addAction( aboutAction );
411 }
412 
413 void MainWindow::createToolBars()
414 {
415     infoLine = new InfoLine();
416     infoLine->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
417     infoLine->setLineWidth( 0 );
418 
419     lineNbField = new QLabel( );
420     lineNbField->setText( "Line 0" );
421     lineNbField->setAlignment( Qt::AlignLeft | Qt::AlignVCenter );
422     lineNbField->setMinimumSize(
423             lineNbField->fontMetrics().size( 0, "Line 0000000") );
424 
425     toolBar = addToolBar( tr("&Toolbar") );
426     toolBar->setIconSize( QSize( 14, 14 ) );
427     toolBar->setMovable( false );
428     toolBar->addAction( openAction );
429     toolBar->addAction( reloadAction );
430     toolBar->addWidget( infoLine );
431     toolBar->addAction( stopAction );
432     toolBar->addWidget( lineNbField );
433 }
434 
435 //
436 // Slots
437 //
438 
439 // Opens the file selection dialog to select a new log file
440 void MainWindow::open()
441 {
442     QString defaultDir = ".";
443 
444     // Default to the path of the current file if there is one
445     if ( auto current = currentCrawlerWidget() )
446     {
447         std::string current_file = session_->getFilename( current );
448         QFileInfo fileInfo = QFileInfo( QString( current_file.c_str() ) );
449         defaultDir = fileInfo.path();
450     }
451 
452     QString fileName = QFileDialog::getOpenFileName(this,
453             tr("Open file"), defaultDir, tr("All files (*)"));
454     if (!fileName.isEmpty())
455         loadFile(fileName);
456 }
457 
458 // Opens a log file from the recent files list
459 void MainWindow::openRecentFile()
460 {
461     QAction* action = qobject_cast<QAction*>(sender());
462     if (action)
463         loadFile(action->data().toString());
464 }
465 
466 // Close current tab
467 void MainWindow::closeTab()
468 {
469     int currentIndex = mainTabWidget_.currentIndex();
470 
471     if ( currentIndex >= 0 )
472     {
473         closeTab(currentIndex);
474     }
475 }
476 
477 // Close all tabs
478 void MainWindow::closeAll()
479 {
480     while ( mainTabWidget_.count() )
481     {
482         closeTab(0);
483     }
484 }
485 
486 // Select all the text in the currently selected view
487 void MainWindow::selectAll()
488 {
489     CrawlerWidget* current = currentCrawlerWidget();
490 
491     if ( current )
492         current->selectAll();
493 }
494 
495 // Copy the currently selected line into the clipboard
496 void MainWindow::copy()
497 {
498     static QClipboard* clipboard = QApplication::clipboard();
499     CrawlerWidget* current = currentCrawlerWidget();
500 
501     if ( current ) {
502         clipboard->setText( current->getSelectedText() );
503 
504         // Put it in the global selection as well (X11 only)
505         clipboard->setText( current->getSelectedText(),
506                 QClipboard::Selection );
507     }
508 }
509 
510 // Display the QuickFind bar
511 void MainWindow::find()
512 {
513     displayQuickFindBar( QuickFindMux::Forward );
514 }
515 
516 // Opens the 'Filters' dialog box
517 void MainWindow::filters()
518 {
519     FiltersDialog dialog(this);
520     signalMux_.connect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
521     dialog.exec();
522     signalMux_.disconnect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
523 }
524 
525 // Opens the 'Options' modal dialog box
526 void MainWindow::options()
527 {
528     OptionsDialog dialog(this);
529     signalMux_.connect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
530     dialog.exec();
531     signalMux_.disconnect(&dialog, SIGNAL( optionsChanged() ), SLOT( applyConfiguration() ));
532 }
533 
534 // Opens the 'About' dialog box.
535 void MainWindow::about()
536 {
537     QMessageBox::about(this, tr("About glogg"),
538             tr("<h2>glogg " GLOGG_VERSION "</h2>"
539                 "<p>A fast, advanced log explorer."
540 #ifdef GLOGG_COMMIT
541                 "<p>Built " GLOGG_DATE " from " GLOGG_COMMIT
542 #endif
543                 "<p><a href=\"http://glogg.bonnefon.org/\">http://glogg.bonnefon.org/</a></p>"
544                 "<p>Copyright &copy; 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Nicolas Bonnefon and other contributors"
545                 "<p>You may modify and redistribute the program under the terms of the GPL (version 3 or later)." ) );
546 }
547 
548 // Opens the 'About Qt' dialog box.
549 void MainWindow::aboutQt()
550 {
551 }
552 
553 void MainWindow::encodingChanged( QAction* action )
554 {
555     int i = 0;
556     for ( i = 0; i < static_cast<int>( Encoding::ENCODING_MAX ); ++i )
557         if ( action == encodingAction[i] )
558             break;
559 
560     LOG(logDEBUG) << "encodingChanged, encoding " << i;
561     currentCrawlerWidget()->setEncoding( static_cast<Encoding>( i ) );
562     updateInfoLine();
563 }
564 
565 void MainWindow::toggleOverviewVisibility( bool isVisible )
566 {
567     std::shared_ptr<Configuration> config =
568         Persistent<Configuration>( "settings" );
569     config->setOverviewVisible( isVisible );
570     emit optionsChanged();
571 }
572 
573 void MainWindow::toggleMainLineNumbersVisibility( bool isVisible )
574 {
575     std::shared_ptr<Configuration> config =
576         Persistent<Configuration>( "settings" );
577     config->setMainLineNumbersVisible( isVisible );
578     emit optionsChanged();
579 }
580 
581 void MainWindow::toggleFilteredLineNumbersVisibility( bool isVisible )
582 {
583     std::shared_ptr<Configuration> config =
584         Persistent<Configuration>( "settings" );
585     config->setFilteredLineNumbersVisible( isVisible );
586     emit optionsChanged();
587 }
588 
589 void MainWindow::changeFollowMode( bool follow )
590 {
591     followAction->setChecked( follow );
592 }
593 
594 void MainWindow::lineNumberHandler( int line )
595 {
596     // The line number received is the internal (starts at 0)
597     lineNbField->setText( tr( "Line %1" ).arg( line + 1 ) );
598 }
599 
600 void MainWindow::updateLoadingProgress( int progress )
601 {
602     LOG(logDEBUG) << "Loading progress: " << progress;
603 
604     QString current_file =
605         session_->getFilename( currentCrawlerWidget() ).c_str();
606 
607     // We ignore 0% and 100% to avoid a flash when the file (or update)
608     // is very short.
609     if ( progress > 0 && progress < 100 ) {
610         infoLine->setText( current_file +
611                 tr( " - Indexing lines... (%1 %)" ).arg( progress ) );
612         infoLine->displayGauge( progress );
613 
614         stopAction->setEnabled( true );
615         reloadAction->setEnabled( false );
616     }
617 }
618 
619 void MainWindow::handleLoadingFinished( LoadingStatus status )
620 {
621     LOG(logDEBUG) << "handleLoadingFinished success=" <<
622         ( status == LoadingStatus::Successful );
623 
624     // No file is loading
625     loadingFileName.clear();
626 
627     if ( status == LoadingStatus::Successful )
628     {
629         updateInfoLine();
630 
631         infoLine->hideGauge();
632         stopAction->setEnabled( false );
633         reloadAction->setEnabled( true );
634 
635         // Now everything is ready, we can finally show the file!
636         currentCrawlerWidget()->show();
637     }
638     else
639     {
640         if ( status == LoadingStatus::NoMemory )
641         {
642             QMessageBox alertBox;
643             alertBox.setText( "Not enough memory." );
644             alertBox.setInformativeText( "The system does not have enough \
645 memory to hold the index for this file. The file will now be closed." );
646             alertBox.setIcon( QMessageBox::Critical );
647             alertBox.exec();
648         }
649 
650         closeTab( mainTabWidget_.currentIndex()  );
651     }
652 
653     // mainTabWidget_.setEnabled( true );
654 }
655 
656 void MainWindow::handleSearchRefreshChanged( int state )
657 {
658     auto config = Persistent<Configuration>( "settings" );
659     config->setSearchAutoRefreshDefault( state == Qt::Checked );
660 }
661 
662 void MainWindow::handleIgnoreCaseChanged( int state )
663 {
664     auto config = Persistent<Configuration>( "settings" );
665     config->setSearchIgnoreCaseDefault( state == Qt::Checked );
666 }
667 
668 void MainWindow::closeTab( int index )
669 {
670     auto widget = dynamic_cast<CrawlerWidget*>(
671             mainTabWidget_.widget( index ) );
672 
673     assert( widget );
674 
675     widget->stopLoading();
676     mainTabWidget_.removeTab( index );
677     session_->close( widget );
678     delete widget;
679 }
680 
681 void MainWindow::currentTabChanged( int index )
682 {
683     LOG(logDEBUG) << "currentTabChanged";
684 
685     if ( index >= 0 )
686     {
687         CrawlerWidget* crawler_widget = dynamic_cast<CrawlerWidget*>(
688                 mainTabWidget_.widget( index ) );
689         signalMux_.setCurrentDocument( crawler_widget );
690         quickFindMux_.registerSelector( crawler_widget );
691 
692         // New tab is set up with fonts etc...
693         emit optionsChanged();
694 
695         // Update the menu bar
696         updateMenuBarFromDocument( crawler_widget );
697 
698         // Update the title bar
699         updateTitleBar( QString(
700                     session_->getFilename( crawler_widget ).c_str() ) );
701     }
702     else
703     {
704         // No tab left
705         signalMux_.setCurrentDocument( nullptr );
706         quickFindMux_.registerSelector( nullptr );
707 
708         infoLine->hideGauge();
709         infoLine->clear();
710 
711         updateTitleBar( QString() );
712     }
713 }
714 
715 void MainWindow::changeQFPattern( const QString& newPattern )
716 {
717     quickFindWidget_.changeDisplayedPattern( newPattern );
718 }
719 
720 void MainWindow::loadFileNonInteractive( const QString& file_name )
721 {
722     LOG(logDEBUG) << "loadFileNonInteractive( "
723         << file_name.toStdString() << " )";
724 
725     loadFile( file_name );
726 
727     // Try to get the window to the front
728     // This is a bit of a hack but has been tested on:
729     // Qt 5.3 / Gnome / Linux
730     // Qt 4.8 / Win7
731 #ifdef _WIN32
732     // Hack copied from http://qt-project.org/forums/viewthread/6164
733     ::SetWindowPos((HWND) effectiveWinId(), HWND_TOPMOST,
734             0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
735     ::SetWindowPos((HWND) effectiveWinId(), HWND_NOTOPMOST,
736             0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
737 #else
738     Qt::WindowFlags window_flags = windowFlags();
739     window_flags |= Qt::WindowStaysOnTopHint;
740     setWindowFlags( window_flags );
741 #endif
742 
743     activateWindow();
744     raise();
745 
746 #ifndef _WIN32
747     window_flags = windowFlags();
748     window_flags &= ~Qt::WindowStaysOnTopHint;
749     setWindowFlags( window_flags );
750 #endif
751 
752     showNormal();
753 }
754 
755 void MainWindow::newVersionNotification( const QString& new_version )
756 {
757     LOG(logDEBUG) << "newVersionNotification( " <<
758         new_version.toStdString() << " )";
759 
760     QMessageBox msgBox;
761     msgBox.setText( QString( "A new version of glogg (%1) is available for download <p>"
762                 "<a href=\"http://glogg.bonnefon.org/download.html\">http://glogg.bonnefon.org/download.html</a>"
763                 ).arg( new_version ) );
764     msgBox.exec();
765 }
766 
767 //
768 // Events
769 //
770 
771 // Closes the application
772 void MainWindow::closeEvent( QCloseEvent *event )
773 {
774     writeSettings();
775     event->accept();
776 }
777 
778 // Accepts the drag event if it looks like a filename
779 void MainWindow::dragEnterEvent( QDragEnterEvent* event )
780 {
781     if ( event->mimeData()->hasFormat( "text/uri-list" ) )
782         event->acceptProposedAction();
783 }
784 
785 // Tries and loads the file if the URL dropped is local
786 void MainWindow::dropEvent( QDropEvent* event )
787 {
788     foreach( const QUrl& url, event->mimeData()->urls() ) {
789         QString fileName = url.toLocalFile();
790         if ( !fileName.isEmpty() ) {
791             loadFile( fileName );
792         }
793     }
794 }
795 
796 void MainWindow::keyPressEvent( QKeyEvent* keyEvent )
797 {
798     LOG(logDEBUG4) << "keyPressEvent received";
799 
800     switch ( (keyEvent->text())[0].toLatin1() ) {
801         case '/':
802             displayQuickFindBar( QuickFindMux::Forward );
803             break;
804         case '?':
805             displayQuickFindBar( QuickFindMux::Backward );
806             break;
807         default:
808             keyEvent->ignore();
809     }
810 
811     if ( !keyEvent->isAccepted() )
812         QMainWindow::keyPressEvent( keyEvent );
813 }
814 
815 //
816 // Private functions
817 //
818 
819 // Create a CrawlerWidget for the passed file, start its loading
820 // and update the title bar.
821 // The loading is done asynchronously.
822 bool MainWindow::loadFile( const QString& fileName )
823 {
824     LOG(logDEBUG) << "loadFile ( " << fileName.toStdString() << " )";
825 
826     // First check if the file is already open...
827     CrawlerWidget* existing_crawler = dynamic_cast<CrawlerWidget*>(
828             session_->getViewIfOpen( fileName.toStdString() ) );
829     if ( existing_crawler ) {
830         // ... and switch to it.
831         mainTabWidget_.setCurrentWidget( existing_crawler );
832 
833         return true;
834     }
835 
836     // Load the file
837     loadingFileName = fileName;
838 
839     try {
840         CrawlerWidget* crawler_widget = dynamic_cast<CrawlerWidget*>(
841                 session_->open( fileName.toStdString(),
842                     []() { return new CrawlerWidget(); } ) );
843         assert( crawler_widget );
844 
845         // We won't show the widget until the file is fully loaded
846         crawler_widget->hide();
847 
848         // We disable the tab widget to avoid having someone switch
849         // tab during loading. (maybe FIXME)
850         //mainTabWidget_.setEnabled( false );
851 
852         int index = mainTabWidget_.addTab(
853                 crawler_widget, strippedName( fileName ) );
854 
855         // Setting the new tab, the user will see a blank page for the duration
856         // of the loading, with no way to switch to another tab
857         mainTabWidget_.setCurrentIndex( index );
858 
859         // Update the recent files list
860         // (reload the list first in case another glogg changed it)
861         GetPersistentInfo().retrieve( "recentFiles" );
862         recentFiles_->addRecent( fileName );
863         GetPersistentInfo().save( "recentFiles" );
864         updateRecentFileActions();
865     }
866     catch ( FileUnreadableErr ) {
867         LOG(logDEBUG) << "Can't open file " << fileName.toStdString();
868         return false;
869     }
870 
871     LOG(logDEBUG) << "Success loading file " << fileName.toStdString();
872     return true;
873 
874 }
875 
876 // Strips the passed filename from its directory part.
877 QString MainWindow::strippedName( const QString& fullFileName ) const
878 {
879     return QFileInfo( fullFileName ).fileName();
880 }
881 
882 // Return the currently active CrawlerWidget, or NULL if none
883 CrawlerWidget* MainWindow::currentCrawlerWidget() const
884 {
885     auto current = dynamic_cast<CrawlerWidget*>(
886             mainTabWidget_.currentWidget() );
887 
888     return current;
889 }
890 
891 // Update the title bar.
892 void MainWindow::updateTitleBar( const QString& file_name )
893 {
894     QString shownName = tr( "Untitled" );
895     if ( !file_name.isEmpty() )
896         shownName = strippedName( file_name );
897 
898     setWindowTitle(
899             tr("%1 - %2").arg(shownName).arg(tr("glogg"))
900 #ifdef GLOGG_COMMIT
901             + " (dev build " GLOGG_VERSION ")"
902 #endif
903             );
904 }
905 
906 // Updates the actions for the recent files.
907 // Must be called after having added a new name to the list.
908 void MainWindow::updateRecentFileActions()
909 {
910     QStringList recent_files = recentFiles_->recentFiles();
911 
912     for ( int j = 0; j < MaxRecentFiles; ++j ) {
913         if ( j < recent_files.count() ) {
914             QString text = tr("&%1 %2").arg(j + 1).arg(strippedName(recent_files[j]));
915             recentFileActions[j]->setText( text );
916             recentFileActions[j]->setToolTip( recent_files[j] );
917             recentFileActions[j]->setData( recent_files[j] );
918             recentFileActions[j]->setVisible( true );
919         }
920         else {
921             recentFileActions[j]->setVisible( false );
922         }
923     }
924 
925     // separatorAction->setVisible(!recentFiles.isEmpty());
926 }
927 
928 // Update our menu bar to match the settings of the crawler
929 // (used when the tab is changed)
930 void MainWindow::updateMenuBarFromDocument( const CrawlerWidget* crawler )
931 {
932     auto encoding = crawler->encodingSetting();
933     encodingAction[static_cast<int>( encoding )]->setChecked( true );
934     bool follow = crawler->isFollowEnabled();
935     followAction->setChecked( follow );
936 }
937 
938 // Update the top info line from the session
939 void MainWindow::updateInfoLine()
940 {
941     QLocale defaultLocale;
942 
943     // Following should always work as we will only receive enter
944     // this slot if there is a crawler connected.
945     QString current_file =
946         session_->getFilename( currentCrawlerWidget() ).c_str();
947 
948     uint64_t fileSize;
949     uint32_t fileNbLine;
950     QDateTime lastModified;
951 
952     session_->getFileInfo( currentCrawlerWidget(),
953             &fileSize, &fileNbLine, &lastModified );
954     if ( lastModified.isValid() ) {
955         const QString date =
956             defaultLocale.toString( lastModified, QLocale::NarrowFormat );
957         infoLine->setText( tr( "%1 (%2 - %3 lines - modified on %4 - %5)" )
958                 .arg(current_file).arg(readableSize(fileSize))
959                 .arg(fileNbLine).arg( date )
960                 .arg(currentCrawlerWidget()->encodingText()) );
961     }
962     else {
963         infoLine->setText( tr( "%1 (%2 - %3 lines - %4)" )
964                 .arg(current_file).arg(readableSize(fileSize))
965                 .arg(fileNbLine)
966                 .arg(currentCrawlerWidget()->encodingText()) );
967     }
968 }
969 
970 // Write settings to permanent storage
971 void MainWindow::writeSettings()
972 {
973     // Save the session
974     // Generate the ordered list of widgets and their topLine
975     std::vector<
976             std::tuple<const ViewInterface*, uint64_t, std::shared_ptr<const ViewContextInterface>>
977         > widget_list;
978     for ( int i = 0; i < mainTabWidget_.count(); ++i )
979     {
980         auto view = dynamic_cast<const ViewInterface*>( mainTabWidget_.widget( i ) );
981         widget_list.push_back( std::make_tuple(
982                 view,
983                 0UL,
984                 view->context() ) );
985     }
986     session_->save( widget_list, saveGeometry() );
987 
988     // User settings
989     GetPersistentInfo().save( QString( "settings" ) );
990 }
991 
992 // Read settings from permanent storage
993 void MainWindow::readSettings()
994 {
995     // Get and restore the session
996     // GetPersistentInfo().retrieve( QString( "session" ) );
997     // SessionInfo session = Persistent<SessionInfo>( "session" );
998     /*
999      * FIXME: should be in the session
1000     crawlerWidget->restoreState( session.crawlerState() );
1001     */
1002 
1003     // History of recent files
1004     GetPersistentInfo().retrieve( QString( "recentFiles" ) );
1005     updateRecentFileActions();
1006 
1007     // GetPersistentInfo().retrieve( QString( "settings" ) );
1008     GetPersistentInfo().retrieve( QString( "filterSet" ) );
1009 }
1010 
1011 void MainWindow::displayQuickFindBar( QuickFindMux::QFDirection direction )
1012 {
1013     LOG(logDEBUG) << "MainWindow::displayQuickFindBar";
1014 
1015     // Warn crawlers so they can save the position of the focus in order
1016     // to do incremental search in the right view.
1017     emit enteringQuickFind();
1018 
1019     quickFindMux_.setDirection( direction );
1020     quickFindWidget_.userActivate();
1021 }
1022 
1023 // Returns the size in human readable format
1024 static QString readableSize( qint64 size )
1025 {
1026     static const QString sizeStrs[] = {
1027         QObject::tr("B"), QObject::tr("KiB"), QObject::tr("MiB"),
1028         QObject::tr("GiB"), QObject::tr("TiB") };
1029 
1030     QLocale defaultLocale;
1031     unsigned int i;
1032     double humanSize = size;
1033 
1034     for ( i=0; i+1 < (sizeof(sizeStrs)/sizeof(QString)) && (humanSize/1024.0) >= 1024.0; i++ )
1035         humanSize /= 1024.0;
1036 
1037     if ( humanSize >= 1024.0 ) {
1038         humanSize /= 1024.0;
1039         i++;
1040     }
1041 
1042     QString output;
1043     if ( i == 0 )
1044         // No decimal part if we display straight bytes.
1045         output = defaultLocale.toString( (int) humanSize );
1046     else
1047         output = defaultLocale.toString( humanSize, 'f', 1 );
1048 
1049     output += QString(" ") + sizeStrs[i];
1050 
1051     return output;
1052 }
1053