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>Copyright © 2009, 2010, 2011, 2012, 2013, 2014 Nicolas Bonnefon and other contributors" 444 "<p>You may modify and redistribute the program under the terms of the GPL (version 3 or later)." ) ); 445 } 446 447 // Opens the 'About Qt' dialog box. 448 void MainWindow::aboutQt() 449 { 450 } 451 452 void MainWindow::toggleOverviewVisibility( bool isVisible ) 453 { 454 std::shared_ptr<Configuration> config = 455 Persistent<Configuration>( "settings" ); 456 config->setOverviewVisible( isVisible ); 457 emit optionsChanged(); 458 } 459 460 void MainWindow::toggleMainLineNumbersVisibility( bool isVisible ) 461 { 462 std::shared_ptr<Configuration> config = 463 Persistent<Configuration>( "settings" ); 464 config->setMainLineNumbersVisible( isVisible ); 465 emit optionsChanged(); 466 } 467 468 void MainWindow::toggleFilteredLineNumbersVisibility( bool isVisible ) 469 { 470 std::shared_ptr<Configuration> config = 471 Persistent<Configuration>( "settings" ); 472 config->setFilteredLineNumbersVisible( isVisible ); 473 emit optionsChanged(); 474 } 475 476 void MainWindow::disableFollow() 477 { 478 followAction->setChecked( false ); 479 } 480 481 void MainWindow::lineNumberHandler( int line ) 482 { 483 // The line number received is the internal (starts at 0) 484 lineNbField->setText( tr( "Line %1" ).arg( line + 1 ) ); 485 } 486 487 void MainWindow::updateLoadingProgress( int progress ) 488 { 489 LOG(logDEBUG) << "Loading progress: " << progress; 490 491 QString current_file = 492 session_->getFilename( currentCrawlerWidget() ).c_str(); 493 494 // We ignore 0% and 100% to avoid a flash when the file (or update) 495 // is very short. 496 if ( progress > 0 && progress < 100 ) { 497 infoLine->setText( current_file + 498 tr( " - Indexing lines... (%1 %)" ).arg( progress ) ); 499 infoLine->displayGauge( progress ); 500 501 stopAction->setEnabled( true ); 502 reloadAction->setEnabled( false ); 503 } 504 } 505 506 void MainWindow::handleLoadingFinished( LoadingStatus status ) 507 { 508 QLocale defaultLocale; 509 510 LOG(logDEBUG) << "handleLoadingFinished success=" << 511 ( status == LoadingStatus::Successful ); 512 513 // No file is loading 514 loadingFileName.clear(); 515 516 if ( status == LoadingStatus::Successful ) 517 { 518 // Following should always work as we will only receive enter 519 // this slot if there is a crawler connected. 520 QString current_file = 521 session_->getFilename( currentCrawlerWidget() ).c_str(); 522 523 uint64_t fileSize; 524 uint32_t fileNbLine; 525 QDateTime lastModified; 526 527 session_->getFileInfo( currentCrawlerWidget(), 528 &fileSize, &fileNbLine, &lastModified ); 529 if ( lastModified.isValid() ) { 530 const QString date = 531 defaultLocale.toString( lastModified, QLocale::NarrowFormat ); 532 infoLine->setText( tr( "%1 (%2 - %3 lines - modified on %4)" ) 533 .arg(current_file).arg(readableSize(fileSize)) 534 .arg(fileNbLine).arg( date ) ); 535 } 536 else { 537 infoLine->setText( tr( "%1 (%2 - %3 lines)" ) 538 .arg(current_file).arg(readableSize(fileSize)) 539 .arg(fileNbLine) ); 540 } 541 542 infoLine->hideGauge(); 543 stopAction->setEnabled( false ); 544 reloadAction->setEnabled( true ); 545 546 // Now everything is ready, we can finally show the file! 547 currentCrawlerWidget()->show(); 548 } 549 else 550 { 551 if ( status == LoadingStatus::NoMemory ) 552 { 553 QMessageBox alertBox; 554 alertBox.setText( "Not enough memory." ); 555 alertBox.setInformativeText( "The system does not have enough \ 556 memory to hold the index for this file. The file will now be closed." ); 557 alertBox.setIcon( QMessageBox::Critical ); 558 alertBox.exec(); 559 } 560 561 closeTab( mainTabWidget_.currentIndex() ); 562 } 563 564 // mainTabWidget_.setEnabled( true ); 565 } 566 567 void MainWindow::closeTab( int index ) 568 { 569 auto widget = dynamic_cast<CrawlerWidget*>( 570 mainTabWidget_.widget( index ) ); 571 572 assert( widget ); 573 574 widget->stopLoading(); 575 mainTabWidget_.removeTab( index ); 576 session_->close( widget ); 577 delete widget; 578 } 579 580 void MainWindow::currentTabChanged( int index ) 581 { 582 LOG(logDEBUG) << "currentTabChanged"; 583 584 if ( index >= 0 ) 585 { 586 CrawlerWidget* crawler_widget = dynamic_cast<CrawlerWidget*>( 587 mainTabWidget_.widget( index ) ); 588 signalMux_.setCurrentDocument( crawler_widget ); 589 quickFindMux_.registerSelector( crawler_widget ); 590 591 // New tab is set up with fonts etc... 592 emit optionsChanged(); 593 594 // Update the title bar 595 updateTitleBar( QString( 596 session_->getFilename( crawler_widget ).c_str() ) ); 597 } 598 else 599 { 600 // No tab left 601 signalMux_.setCurrentDocument( nullptr ); 602 quickFindMux_.registerSelector( nullptr ); 603 604 infoLine->hideGauge(); 605 infoLine->clear(); 606 607 updateTitleBar( QString() ); 608 } 609 } 610 611 void MainWindow::changeQFPattern( const QString& newPattern ) 612 { 613 quickFindWidget_.changeDisplayedPattern( newPattern ); 614 } 615 616 void MainWindow::loadFileNonInteractive( const QString& file_name ) 617 { 618 LOG(logDEBUG) << "loadFileNonInteractive( " 619 << file_name.toStdString() << " )"; 620 621 loadFile( file_name ); 622 } 623 624 // 625 // Events 626 // 627 628 // Closes the application 629 void MainWindow::closeEvent( QCloseEvent *event ) 630 { 631 writeSettings(); 632 event->accept(); 633 } 634 635 // Accepts the drag event if it looks like a filename 636 void MainWindow::dragEnterEvent( QDragEnterEvent* event ) 637 { 638 if ( event->mimeData()->hasFormat( "text/uri-list" ) ) 639 event->acceptProposedAction(); 640 } 641 642 // Tries and loads the file if the URL dropped is local 643 void MainWindow::dropEvent( QDropEvent* event ) 644 { 645 QList<QUrl> urls = event->mimeData()->urls(); 646 if ( urls.isEmpty() ) 647 return; 648 649 QString fileName = urls.first().toLocalFile(); 650 if ( fileName.isEmpty() ) 651 return; 652 653 loadFile( fileName ); 654 } 655 656 void MainWindow::keyPressEvent( QKeyEvent* keyEvent ) 657 { 658 LOG(logDEBUG4) << "keyPressEvent received"; 659 660 switch ( (keyEvent->text())[0].toLatin1() ) { 661 case '/': 662 displayQuickFindBar( QuickFindMux::Forward ); 663 break; 664 case '?': 665 displayQuickFindBar( QuickFindMux::Backward ); 666 break; 667 default: 668 keyEvent->ignore(); 669 } 670 671 if ( !keyEvent->isAccepted() ) 672 QMainWindow::keyPressEvent( keyEvent ); 673 } 674 675 // 676 // Private functions 677 // 678 679 // Create a CrawlerWidget for the passed file, start its loading 680 // and update the title bar. 681 // The loading is done asynchronously. 682 bool MainWindow::loadFile( const QString& fileName ) 683 { 684 LOG(logDEBUG) << "loadFile ( " << fileName.toStdString() << " )"; 685 686 // First check if the file is already open... 687 CrawlerWidget* existing_crawler = dynamic_cast<CrawlerWidget*>( 688 session_->getViewIfOpen( fileName.toStdString() ) ); 689 if ( existing_crawler ) { 690 // ... and switch to it. 691 mainTabWidget_.setCurrentWidget( existing_crawler ); 692 693 return true; 694 } 695 696 // Load the file 697 loadingFileName = fileName; 698 699 try { 700 CrawlerWidget* crawler_widget = dynamic_cast<CrawlerWidget*>( 701 session_->open( fileName.toStdString(), 702 []() { return new CrawlerWidget(); } ) ); 703 assert( crawler_widget ); 704 705 // We won't show the widget until the file is fully loaded 706 crawler_widget->hide(); 707 708 // We disable the tab widget to avoid having someone switch 709 // tab during loading. (maybe FIXME) 710 //mainTabWidget_.setEnabled( false ); 711 712 int index = mainTabWidget_.addTab( 713 crawler_widget, strippedName( fileName ) ); 714 715 // Setting the new tab, the user will see a blank page for the duration 716 // of the loading, with no way to switch to another tab 717 mainTabWidget_.setCurrentIndex( index ); 718 719 // Update the recent files list 720 // (reload the list first in case another glogg changed it) 721 GetPersistentInfo().retrieve( "recentFiles" ); 722 recentFiles_->addRecent( fileName ); 723 GetPersistentInfo().save( "recentFiles" ); 724 updateRecentFileActions(); 725 } 726 catch ( FileUnreadableErr ) { 727 LOG(logDEBUG) << "Can't open file " << fileName.toStdString(); 728 return false; 729 } 730 731 LOG(logDEBUG) << "Success loading file " << fileName.toStdString(); 732 return true; 733 734 } 735 736 // Strips the passed filename from its directory part. 737 QString MainWindow::strippedName( const QString& fullFileName ) const 738 { 739 return QFileInfo( fullFileName ).fileName(); 740 } 741 742 // Return the currently active CrawlerWidget, or NULL if none 743 CrawlerWidget* MainWindow::currentCrawlerWidget() const 744 { 745 auto current = dynamic_cast<CrawlerWidget*>( 746 mainTabWidget_.currentWidget() ); 747 748 return current; 749 } 750 751 // Update the title bar. 752 void MainWindow::updateTitleBar( const QString& file_name ) 753 { 754 QString shownName = tr( "Untitled" ); 755 if ( !file_name.isEmpty() ) 756 shownName = strippedName( file_name ); 757 758 setWindowTitle( 759 tr("%1 - %2").arg(shownName).arg(tr("glogg")) 760 #ifdef GLOGG_COMMIT 761 + " (dev build " GLOGG_VERSION ")" 762 #endif 763 ); 764 } 765 766 // Updates the actions for the recent files. 767 // Must be called after having added a new name to the list. 768 void MainWindow::updateRecentFileActions() 769 { 770 QStringList recent_files = recentFiles_->recentFiles(); 771 772 for ( int j = 0; j < MaxRecentFiles; ++j ) { 773 if ( j < recent_files.count() ) { 774 QString text = tr("&%1 %2").arg(j + 1).arg(strippedName(recent_files[j])); 775 recentFileActions[j]->setText( text ); 776 recentFileActions[j]->setToolTip( recent_files[j] ); 777 recentFileActions[j]->setData( recent_files[j] ); 778 recentFileActions[j]->setVisible( true ); 779 } 780 else { 781 recentFileActions[j]->setVisible( false ); 782 } 783 } 784 785 // separatorAction->setVisible(!recentFiles.isEmpty()); 786 } 787 788 // Write settings to permanent storage 789 void MainWindow::writeSettings() 790 { 791 // Save the session 792 // Generate the ordered list of widgets and their topLine 793 std::vector< 794 std::tuple<const ViewInterface*, uint64_t, std::shared_ptr<const ViewContextInterface>> 795 > widget_list; 796 for ( int i = 0; i < mainTabWidget_.count(); ++i ) 797 { 798 auto view = dynamic_cast<const ViewInterface*>( mainTabWidget_.widget( i ) ); 799 widget_list.push_back( std::make_tuple( 800 view, 801 0UL, 802 view->context() ) ); 803 } 804 session_->save( widget_list ); 805 //SessionInfo& session = Persistent<SessionInfo>( "session" ); 806 //session.setGeometry( saveGeometry() ); 807 //session.setCrawlerState( crawlerWidget->saveState() ); 808 //GetPersistentInfo().save( QString( "session" ) ); 809 810 // User settings 811 GetPersistentInfo().save( QString( "settings" ) ); 812 } 813 814 // Read settings from permanent storage 815 void MainWindow::readSettings() 816 { 817 // Get and restore the session 818 // GetPersistentInfo().retrieve( QString( "session" ) ); 819 // SessionInfo session = Persistent<SessionInfo>( "session" ); 820 //restoreGeometry( session.geometry() ); 821 /* 822 * FIXME: should be in the session 823 crawlerWidget->restoreState( session.crawlerState() ); 824 */ 825 826 // History of recent files 827 GetPersistentInfo().retrieve( QString( "recentFiles" ) ); 828 updateRecentFileActions(); 829 830 // GetPersistentInfo().retrieve( QString( "settings" ) ); 831 GetPersistentInfo().retrieve( QString( "filterSet" ) ); 832 } 833 834 void MainWindow::displayQuickFindBar( QuickFindMux::QFDirection direction ) 835 { 836 LOG(logDEBUG) << "MainWindow::displayQuickFindBar"; 837 838 // Warn crawlers so they can save the position of the focus in order 839 // to do incremental search in the right view. 840 emit enteringQuickFind(); 841 842 quickFindMux_.setDirection( direction ); 843 quickFindWidget_.userActivate(); 844 } 845 846 // Returns the size in human readable format 847 static QString readableSize( qint64 size ) 848 { 849 static const QString sizeStrs[] = { 850 QObject::tr("B"), QObject::tr("KiB"), QObject::tr("MiB"), 851 QObject::tr("GiB"), QObject::tr("TiB") }; 852 853 QLocale defaultLocale; 854 unsigned int i; 855 double humanSize = size; 856 857 for ( i=0; i+1 < (sizeof(sizeStrs)/sizeof(QString)) && (humanSize/1024.0) >= 1024.0; i++ ) 858 humanSize /= 1024.0; 859 860 if ( humanSize >= 1024.0 ) { 861 humanSize /= 1024.0; 862 i++; 863 } 864 865 QString output; 866 if ( i == 0 ) 867 // No decimal part if we display straight bytes. 868 output = defaultLocale.toString( (int) humanSize ); 869 else 870 output = defaultLocale.toString( humanSize, 'f', 1 ); 871 872 output += QString(" ") + sizeStrs[i]; 873 874 return output; 875 } 876