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