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