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
MainWindow(std::unique_ptr<Session> session,std::shared_ptr<ExternalCommunicator> external_communicator)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
reloadGeometry()181 void MainWindow::reloadGeometry()
182 {
183 QByteArray geometry;
184
185 session_->storedGeometry( &geometry );
186 restoreGeometry( geometry );
187 }
188
reloadSession()189 void MainWindow::reloadSession()
190 {
191 int current_file_index = -1;
192
193 for ( auto open_file: session_->restore(
194 []() { return new CrawlerWidget(); },
195 ¤t_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
loadInitialFile(QString fileName)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
startBackgroundTasks()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
createActions()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
createMenus()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
createToolBars()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
open()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
openRecentFile()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
closeTab()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
closeAll()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
selectAll()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
copy()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
find()511 void MainWindow::find()
512 {
513 displayQuickFindBar( QuickFindMux::Forward );
514 }
515
516 // Opens the 'Filters' dialog box
filters()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
options()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.
about()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 © 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.
aboutQt()549 void MainWindow::aboutQt()
550 {
551 }
552
encodingChanged(QAction * action)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
toggleOverviewVisibility(bool isVisible)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
toggleMainLineNumbersVisibility(bool isVisible)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
toggleFilteredLineNumbersVisibility(bool isVisible)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
changeFollowMode(bool follow)589 void MainWindow::changeFollowMode( bool follow )
590 {
591 followAction->setChecked( follow );
592 }
593
lineNumberHandler(int line)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
updateLoadingProgress(int progress)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
handleLoadingFinished(LoadingStatus status)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
handleSearchRefreshChanged(int state)656 void MainWindow::handleSearchRefreshChanged( int state )
657 {
658 auto config = Persistent<Configuration>( "settings" );
659 config->setSearchAutoRefreshDefault( state == Qt::Checked );
660 }
661
handleIgnoreCaseChanged(int state)662 void MainWindow::handleIgnoreCaseChanged( int state )
663 {
664 auto config = Persistent<Configuration>( "settings" );
665 config->setSearchIgnoreCaseDefault( state == Qt::Checked );
666 }
667
closeTab(int index)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
currentTabChanged(int index)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
changeQFPattern(const QString & newPattern)715 void MainWindow::changeQFPattern( const QString& newPattern )
716 {
717 quickFindWidget_.changeDisplayedPattern( newPattern );
718 }
719
loadFileNonInteractive(const QString & file_name)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
newVersionNotification(const QString & new_version)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
closeEvent(QCloseEvent * event)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
dragEnterEvent(QDragEnterEvent * event)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
dropEvent(QDropEvent * event)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
keyPressEvent(QKeyEvent * keyEvent)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.
loadFile(const QString & fileName)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.
strippedName(const QString & fullFileName) const877 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
currentCrawlerWidget() const883 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.
updateTitleBar(const QString & file_name)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.
updateRecentFileActions()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)
updateMenuBarFromDocument(const CrawlerWidget * crawler)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
updateInfoLine()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
writeSettings()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
readSettings()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
displayQuickFindBar(QuickFindMux::QFDirection direction)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
readableSize(qint64 size)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