xref: /glogg/src/crawlerwidget.cpp (revision f0708ca81489bc71275a0d193395638d59f0a661)
1 /*
2  * Copyright (C) 2009, 2010, 2011, 2012, 2013 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 the CrawlerWidget class.
21 // It is responsible for creating and managing the two views and all
22 // the UI elements.  It implements the connection between the UI elements.
23 // It also owns the sets of data (full and filtered).
24 
25 #include "log.h"
26 
27 #include <Qt>
28 #include <QApplication>
29 #include <QFile>
30 #include <QLineEdit>
31 #include <QFileInfo>
32 #include <QKeyEvent>
33 #include <QStandardItemModel>
34 #include <QHeaderView>
35 #include <QListView>
36 
37 #include "crawlerwidget.h"
38 
39 #include "quickfindpattern.h"
40 #include "overview.h"
41 #include "infoline.h"
42 #include "savedsearches.h"
43 #include "quickfindwidget.h"
44 #include "persistentinfo.h"
45 #include "configuration.h"
46 
47 // Palette for error signaling (yellow background)
48 const QPalette CrawlerWidget::errorPalette( QColor( "yellow" ) );
49 
50 // Constructor makes all the child widgets and set up connections.
51 CrawlerWidget::CrawlerWidget(SavedSearches* searches, QWidget *parent)
52         : QSplitter(parent)
53 {
54     setOrientation(Qt::Vertical);
55 
56     // Initialise internal data (with empty file and search)
57     logData_          = new LogData();
58     logFilteredData_  = logData_->getNewFilteredData();
59 
60     // The matches overview
61     overview_ = new Overview();
62 
63     // Initialise the QF Mux to send requests from the QFWidget
64     // to the right window
65     quickFindMux_ = new QuickFindMux( this );
66 
67     // The views
68     bottomWindow = new QWidget;
69     overviewWidget_ = new OverviewWidget();
70     logMainView     = new LogMainView(
71             logData_, quickFindMux_->getPattern(), overview_, overviewWidget_ );
72     filteredView    = new FilteredView(
73             logFilteredData_, quickFindMux_->getPattern() );
74 
75     overviewWidget_->setOverview( overview_ );
76     overviewWidget_->setParent( logMainView );
77 
78     savedSearches = searches;
79 
80     quickFindMux_->registerSearchable( logMainView );
81     quickFindMux_->registerSearchable( filteredView );
82 
83     // Construct the visibility button
84     visibilityModel_ = new QStandardItemModel( this );
85 
86     QStandardItem *marksAndMatchesItem = new QStandardItem( tr( "Marks and matches" ) );
87     QPixmap marksAndMatchesPixmap( 16, 10 );
88     marksAndMatchesPixmap.fill( Qt::gray );
89     marksAndMatchesItem->setIcon( QIcon( marksAndMatchesPixmap ) );
90     marksAndMatchesItem->setData( FilteredView::MarksAndMatches );
91     visibilityModel_->appendRow( marksAndMatchesItem );
92 
93     QStandardItem *marksItem = new QStandardItem( tr( "Marks" ) );
94     QPixmap marksPixmap( 16, 10 );
95     marksPixmap.fill( Qt::blue );
96     marksItem->setIcon( QIcon( marksPixmap ) );
97     marksItem->setData( FilteredView::MarksOnly );
98     visibilityModel_->appendRow( marksItem );
99 
100     QStandardItem *matchesItem = new QStandardItem( tr( "Matches" ) );
101     QPixmap matchesPixmap( 16, 10 );
102     matchesPixmap.fill( Qt::red );
103     matchesItem->setIcon( QIcon( matchesPixmap ) );
104     matchesItem->setData( FilteredView::MatchesOnly );
105     visibilityModel_->appendRow( matchesItem );
106 
107     QListView *visibilityView = new QListView( this );
108     visibilityView->setMovement( QListView::Static );
109     visibilityView->setMinimumWidth( 170 ); // Only needed with custom style-sheet
110 
111     visibilityBox = new QComboBox();
112     visibilityBox->setModel( visibilityModel_ );
113     visibilityBox->setView( visibilityView );
114 
115     // Select "Marks and matches" by default (same default as the filtered view)
116     visibilityBox->setCurrentIndex( 0 );
117 
118     // TODO: Maybe there is some way to set the popup width to be
119     // sized-to-content (as it is when the stylesheet is not overriden) in the
120     // stylesheet as opposed to setting a hard min-width on the view above.
121     visibilityBox->setStyleSheet( " \
122         QComboBox:on {\
123             padding: 1px 2px 1px 6px;\
124             width: 19px;\
125         } \
126         QComboBox:!on {\
127             padding: 1px 2px 1px 7px;\
128             width: 19px;\
129             height: 16px;\
130             border: 1px solid gray;\
131         } \
132         QComboBox::drop-down::down-arrow {\
133             width: 0px;\
134             border-width: 0px;\
135         } \
136 " );
137 
138     // Construct the Search Info line
139     searchInfoLine = new InfoLine();
140     searchInfoLine->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
141     searchInfoLine->setLineWidth( 1 );
142     searchInfoLineDefaultPalette = searchInfoLine->palette();
143 
144     ignoreCaseCheck = new QCheckBox( "Ignore &case" );
145     searchRefreshCheck = new QCheckBox( "Auto-&refresh" );
146 
147     // Construct the Search line
148     searchLabel = new QLabel(tr("&Text: "));
149     searchLineEdit = new QComboBox;
150     searchLineEdit->setEditable( true );
151     searchLineEdit->setCompleter( 0 );
152     searchLineEdit->addItems( savedSearches->recentSearches() );
153     searchLineEdit->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
154     searchLineEdit->setSizeAdjustPolicy( QComboBox::AdjustToMinimumContentsLengthWithIcon );
155 
156     searchLabel->setBuddy( searchLineEdit );
157 
158     searchButton = new QToolButton();
159     searchButton->setText( tr("&Search") );
160     searchButton->setAutoRaise( true );
161 
162     stopButton = new QToolButton();
163     stopButton->setIcon( QIcon(":/images/stop16.png") );
164     stopButton->setAutoRaise( true );
165     stopButton->setEnabled( false );
166 
167     // Construct the QuickFind bar
168     quickFindWidget_ = new QuickFindWidget();
169 
170     QHBoxLayout* searchLineLayout = new QHBoxLayout;
171     searchLineLayout->addWidget(searchLabel);
172     searchLineLayout->addWidget(searchLineEdit);
173     searchLineLayout->addWidget(searchButton);
174     searchLineLayout->addWidget(stopButton);
175     searchLineLayout->setContentsMargins(6, 0, 6, 0);
176     stopButton->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
177     searchButton->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
178 
179     QHBoxLayout* searchInfoLineLayout = new QHBoxLayout;
180     searchInfoLineLayout->addWidget( visibilityBox );
181     searchInfoLineLayout->addWidget( searchInfoLine );
182     searchInfoLineLayout->addWidget( ignoreCaseCheck );
183     searchInfoLineLayout->addWidget( searchRefreshCheck );
184 
185     // Construct the bottom window
186     quickFindWidget_->hide();
187     QVBoxLayout* bottomMainLayout = new QVBoxLayout;
188     bottomMainLayout->addLayout(searchLineLayout);
189     bottomMainLayout->addLayout(searchInfoLineLayout);
190     bottomMainLayout->addWidget(filteredView);
191     bottomMainLayout->addWidget(quickFindWidget_);
192     bottomMainLayout->setContentsMargins(2, 1, 2, 1);
193     bottomWindow->setLayout(bottomMainLayout);
194 
195     addWidget( logMainView );
196     addWidget( bottomWindow );
197 
198     // Default splitter position (usually overridden by the config file)
199     QList<int> splitterSizes;
200     splitterSizes += 400;
201     splitterSizes += 100;
202     setSizes( splitterSizes );
203 
204     // Connect the signals
205     connect(searchLineEdit->lineEdit(), SIGNAL( returnPressed() ),
206             searchButton, SIGNAL( clicked() ));
207     connect(searchLineEdit->lineEdit(), SIGNAL( textEdited( const QString& ) ),
208             this, SLOT( searchTextChangeHandler() ));
209     connect(searchButton, SIGNAL( clicked() ),
210             this, SLOT( startNewSearch() ) );
211     connect(stopButton, SIGNAL( clicked() ),
212             this, SLOT( stopSearch() ) );
213 
214     connect(visibilityBox, SIGNAL( currentIndexChanged( int ) ),
215             this, SLOT( changeFilteredViewVisibility( int ) ) );
216 
217     connect(logMainView, SIGNAL( newSelection( int ) ),
218             logMainView, SLOT( update() ) );
219     connect(filteredView, SIGNAL( newSelection( int ) ),
220             this, SLOT( jumpToMatchingLine( int ) ) );
221     connect(filteredView, SIGNAL( newSelection( int ) ),
222             filteredView, SLOT( update() ) );
223     connect(logMainView, SIGNAL( updateLineNumber( int ) ),
224             this, SIGNAL( updateLineNumber( int ) ) );
225     connect(logMainView, SIGNAL( markLine( qint64 ) ),
226             this, SLOT( markLineFromMain( qint64 ) ) );
227     connect(filteredView, SIGNAL( markLine( qint64 ) ),
228             this, SLOT( markLineFromFiltered( qint64 ) ) );
229 
230     connect(logMainView, SIGNAL( addToSearch( const QString& ) ),
231             this, SLOT( addToSearch( const QString& ) ) );
232     connect(filteredView, SIGNAL( addToSearch( const QString& ) ),
233             this, SLOT( addToSearch( const QString& ) ) );
234 
235     connect(filteredView, SIGNAL( mouseHoveredOverLine( qint64 ) ),
236             this, SLOT( mouseHoveredOverMatch( qint64 ) ) );
237     connect(filteredView, SIGNAL( mouseLeftHoveringZone() ),
238             overviewWidget_, SLOT( removeHighlight() ) );
239 
240     // Follow option (up and down)
241     connect(this, SIGNAL( followSet( bool ) ),
242             logMainView, SLOT( followSet( bool ) ) );
243     connect(this, SIGNAL( followSet( bool ) ),
244             filteredView, SLOT( followSet( bool ) ) );
245     connect(logMainView, SIGNAL( followDisabled() ),
246             this, SIGNAL( followDisabled() ) );
247     connect(filteredView, SIGNAL( followDisabled() ),
248             this, SIGNAL( followDisabled() ) );
249 
250     connect( logFilteredData_, SIGNAL( searchProgressed( int, int ) ),
251             this, SLOT( updateFilteredView( int, int ) ) );
252 
253     // QuickFind
254     connect( quickFindWidget_, SIGNAL( close() ),
255              this, SLOT( hideQuickFindBar() ) );
256     connect( quickFindWidget_, SIGNAL( patternConfirmed( const QString&, bool ) ),
257              quickFindMux_, SLOT( confirmPattern( const QString&, bool ) ) );
258     connect( quickFindWidget_, SIGNAL( patternUpdated( const QString&, bool ) ),
259              quickFindMux_, SLOT( setNewPattern( const QString&, bool ) ) );
260     connect( quickFindWidget_, SIGNAL( cancelSearch() ),
261              quickFindMux_, SLOT( cancelSearch() ) );
262     connect( quickFindWidget_, SIGNAL( searchForward() ),
263              quickFindMux_, SLOT( searchForward() ) );
264     connect( quickFindWidget_, SIGNAL( searchBackward() ),
265              quickFindMux_, SLOT( searchBackward() ) );
266     connect( quickFindWidget_, SIGNAL( searchNext() ),
267              quickFindMux_, SLOT( searchNext() ) );
268 
269     // QuickFind changes coming from the views
270     connect( quickFindMux_, SIGNAL( patternChanged( const QString& ) ),
271              this, SLOT( changeQFPattern( const QString& ) ) );
272     connect( quickFindMux_, SIGNAL( notify( const QFNotification& ) ),
273              quickFindWidget_, SLOT( notify( const QFNotification& ) ) );
274     connect( quickFindMux_, SIGNAL( clearNotification() ),
275              quickFindWidget_, SLOT( clearNotification() ) );
276 
277     // Sent load file update to MainWindow (for status update)
278     connect( logData_, SIGNAL( loadingProgressed( int ) ),
279             this, SIGNAL( loadingProgressed( int ) ) );
280     connect( logData_, SIGNAL( loadingFinished( bool ) ),
281             this, SLOT( loadingFinishedHandler( bool ) ) );
282     connect( logData_, SIGNAL( fileChanged( LogData::MonitoredFileStatus ) ),
283             this, SLOT( fileChangedHandler( LogData::MonitoredFileStatus ) ) );
284 
285     // Search auto-refresh
286     connect( searchRefreshCheck, SIGNAL( stateChanged( int ) ),
287             this, SLOT( searchRefreshChangedHandler( int ) ) );
288 }
289 
290 // Start the asynchronous loading of a file.
291 bool CrawlerWidget::readFile( const QString& fileName, int )
292 {
293     QFileInfo fileInfo( fileName );
294     if ( fileInfo.isReadable() )
295     {
296         LOG(logDEBUG) << "Entering readFile " << fileName.toStdString();
297 
298         // First we cancel any in progress search and loading
299         stopLoading();
300 
301         // The file exist, so we invalidate the search, remove all marks
302         // and redraw the screen.
303         replaceCurrentSearch( "" );
304         logFilteredData_->clearMarks();
305         logData_->attachFile( fileName );
306         logMainView->updateData();
307 
308         // Forbid starting a search when loading in progress
309         // searchButton->setEnabled( false );
310 
311         return true;
312     }
313     else {
314         return false;
315     }
316 }
317 
318 void CrawlerWidget::stopLoading()
319 {
320     logFilteredData_->interruptSearch();
321     logData_->interruptLoading();
322 }
323 
324 void CrawlerWidget::getFileInfo( qint64* fileSize, int* fileNbLine,
325        QDateTime* lastModified ) const
326 {
327     *fileSize = logData_->getFileSize();
328     *fileNbLine = logData_->getNbLine();
329     *lastModified = logData_->getLastModifiedDate();
330 }
331 
332 // The top line is first one on the main display
333 int CrawlerWidget::getTopLine() const
334 {
335     return logMainView->getTopLine();
336 }
337 
338 QString CrawlerWidget::getSelectedText() const
339 {
340     if ( filteredView->hasFocus() )
341         return filteredView->getSelection();
342     else
343         return logMainView->getSelection();
344 }
345 
346 void CrawlerWidget::selectAll()
347 {
348     activeView()->selectAll();
349 }
350 
351 // Return a pointer to the view in which we should do the QuickFind
352 SearchableWidgetInterface* CrawlerWidget::getActiveSearchable() const
353 {
354     QWidget* searchableWidget;
355 
356     // Search in the window that has focus, or the window where 'Find' was
357     // called from, or the main window.
358     if ( filteredView->hasFocus() || logMainView->hasFocus() )
359         searchableWidget = QApplication::focusWidget();
360     else
361         searchableWidget = qfSavedFocus_;
362 
363     if ( AbstractLogView* view = qobject_cast<AbstractLogView*>( searchableWidget ) )
364         return view;
365     else
366         return logMainView;
367 }
368 
369 //
370 // Protected functions
371 //
372 void CrawlerWidget::doSetLogData( std::shared_ptr<LogData> log_data )
373 {
374     logData_ = log_data.get();
375 }
376 
377 void CrawlerWidget::doSetLogFilteredData( std::shared_ptr<LogFilteredData> filtered_data )
378 {
379     logFilteredData_ = filtered_data.get();
380 }
381 
382 //
383 // Events handlers
384 //
385 
386 void CrawlerWidget::keyPressEvent( QKeyEvent* keyEvent )
387 {
388     LOG(logDEBUG4) << "keyPressEvent received";
389 
390     switch ( (keyEvent->text())[0].toLatin1() ) {
391         case '/':
392             displayQuickFindBar( QuickFindMux::Forward );
393             break;
394         case '?':
395             displayQuickFindBar( QuickFindMux::Backward );
396             break;
397         default:
398             keyEvent->ignore();
399     }
400 
401     if ( !keyEvent->isAccepted() )
402         QSplitter::keyPressEvent( keyEvent );
403 }
404 
405 //
406 // Slots
407 //
408 
409 void CrawlerWidget::startNewSearch()
410 {
411     // Record the search line in the recent list
412     // (reload the list first in case another glogg changed it)
413     GetPersistentInfo().retrieve( "savedSearches" );
414     savedSearches->addRecent( searchLineEdit->currentText() );
415     GetPersistentInfo().save( "savedSearches" );
416 
417     // Update the SearchLine (history)
418     updateSearchCombo();
419     // Call the private function to do the search
420     replaceCurrentSearch( searchLineEdit->currentText() );
421 }
422 
423 void CrawlerWidget::stopSearch()
424 {
425     logFilteredData_->interruptSearch();
426     searchState_.stopSearch();
427     printSearchInfoMessage();
428 }
429 
430 // When receiving the 'newDataAvailable' signal from LogFilteredData
431 void CrawlerWidget::updateFilteredView( int nbMatches, int progress )
432 {
433     LOG(logDEBUG) << "updateFilteredView received.";
434 
435     if ( progress == 100 ) {
436         // Searching done
437         printSearchInfoMessage( nbMatches );
438         searchInfoLine->hideGauge();
439         // De-activate the stop button
440         stopButton->setEnabled( false );
441     }
442     else {
443         // Search in progress
444         // We ignore 0% and 100% to avoid a flash when the search is very short
445         if ( progress > 0 ) {
446             searchInfoLine->setText(
447                     tr("Search in progress (%1 %)... %2 match%3 found so far.")
448                     .arg( progress )
449                     .arg( nbMatches )
450                     .arg( nbMatches > 1 ? "es" : "" ) );
451             searchInfoLine->displayGauge( progress );
452         }
453     }
454 
455     // Recompute the content of the filtered window.
456     filteredView->updateData();
457 
458     // Update the match overview
459     overview_->updateData( logData_->getNbLine() );
460 
461     // Also update the top window for the coloured bullets.
462     update();
463 }
464 
465 void CrawlerWidget::jumpToMatchingLine(int filteredLineNb)
466 {
467     int mainViewLine = logFilteredData_->getMatchingLineNumber(filteredLineNb);
468     logMainView->selectAndDisplayLine(mainViewLine);  // FIXME: should be done with a signal.
469 }
470 
471 void CrawlerWidget::markLineFromMain( qint64 line )
472 {
473     if ( logFilteredData_->isLineMarked( line ) )
474         logFilteredData_->deleteMark( line );
475     else
476         logFilteredData_->addMark( line );
477 
478     // Recompute the content of the filtered window.
479     filteredView->updateData();
480 
481     // Update the match overview
482     overview_->updateData( logData_->getNbLine() );
483 
484     // Also update the top window for the coloured bullets.
485     update();
486 }
487 
488 void CrawlerWidget::markLineFromFiltered( qint64 line )
489 {
490     qint64 line_in_file = logFilteredData_->getMatchingLineNumber( line );
491     if ( logFilteredData_->filteredLineTypeByIndex( line )
492             == LogFilteredData::Mark )
493         logFilteredData_->deleteMark( line_in_file );
494     else
495         logFilteredData_->addMark( line_in_file );
496 
497     // Recompute the content of the filtered window.
498     filteredView->updateData();
499 
500     // Update the match overview
501     overview_->updateData( logData_->getNbLine() );
502 
503     // Also update the top window for the coloured bullets.
504     update();
505 }
506 
507 void CrawlerWidget::applyConfiguration()
508 {
509     Configuration& config = Persistent<Configuration>( "settings" );
510     QFont font = config.mainFont();
511 
512     LOG(logDEBUG) << "CrawlerWidget::applyConfiguration";
513 
514     // Whatever font we use, we should NOT use kerning
515     font.setKerning( false );
516     font.setFixedPitch( true );
517 #if QT_VERSION > 0x040700
518     // Necessary on systems doing subpixel positionning (e.g. Ubuntu 12.04)
519     font.setStyleStrategy( QFont::ForceIntegerMetrics );
520 #endif
521     logMainView->setFont(font);
522     filteredView->setFont(font);
523 
524     logMainView->setLineNumbersVisible( config.mainLineNumbersVisible() );
525     filteredView->setLineNumbersVisible( config.filteredLineNumbersVisible() );
526 
527     overview_->setVisible( config.isOverviewVisible() );
528     logMainView->refreshOverview();
529 
530     logMainView->updateDisplaySize();
531     logMainView->update();
532     filteredView->updateDisplaySize();
533     filteredView->update();
534 
535     // Update the SearchLine (history)
536     updateSearchCombo();
537 }
538 
539 void CrawlerWidget::loadingFinishedHandler( bool success )
540 {
541     // We need to refresh the main window because the view lines on the
542     // overview have probably changed.
543     overview_->updateData( logData_->getNbLine() );
544 
545     // FIXME, handle topLine
546     // logMainView->updateData( logData_, topLine );
547     logMainView->updateData();
548 
549     // searchButton->setEnabled( true );
550 
551     // See if we need to auto-refresh the search
552     if ( searchState_.isAutorefreshAllowed() ) {
553         LOG(logDEBUG) << "Refreshing the search";
554         logFilteredData_->updateSearch();
555     }
556 
557     emit loadingFinished( success );
558 }
559 
560 void CrawlerWidget::fileChangedHandler( LogData::MonitoredFileStatus status )
561 {
562     // Handle the case where the file has been truncated
563     if ( status == LogData::Truncated ) {
564         // Clear all marks (TODO offer the option to keep them)
565         logFilteredData_->clearMarks();
566         if ( ! searchInfoLine->text().isEmpty() ) {
567             // Invalidate the search
568             logFilteredData_->clearSearch();
569             filteredView->updateData();
570             searchState_.truncateFile();
571             printSearchInfoMessage();
572         }
573     }
574 }
575 
576 void CrawlerWidget::displayQuickFindBar( QuickFindMux::QFDirection direction )
577 {
578     LOG(logDEBUG) << "CrawlerWidget::displayQuickFindBar";
579 
580     // Remember who had the focus
581     qfSavedFocus_ = QApplication::focusWidget();
582 
583     quickFindMux_->setDirection( direction );
584     quickFindWidget_->userActivate();
585 }
586 
587 void CrawlerWidget::hideQuickFindBar()
588 {
589     // Restore the focus once the QFBar has been hidden
590     qfSavedFocus_->setFocus();
591 }
592 
593 void CrawlerWidget::changeQFPattern( const QString& newPattern )
594 {
595     quickFindWidget_->changeDisplayedPattern( newPattern );
596 }
597 
598 // Returns a pointer to the window in which the search should be done
599 AbstractLogView* CrawlerWidget::activeView() const
600 {
601     QWidget* activeView;
602 
603     // Search in the window that has focus, or the window where 'Find' was
604     // called from, or the main window.
605     if ( filteredView->hasFocus() || logMainView->hasFocus() )
606         activeView = QApplication::focusWidget();
607     else
608         activeView = qfSavedFocus_;
609 
610     if ( AbstractLogView* view = qobject_cast<AbstractLogView*>( activeView ) )
611         return view;
612     else
613         return logMainView;
614 }
615 
616 void CrawlerWidget::searchForward()
617 {
618     LOG(logDEBUG) << "CrawlerWidget::searchForward";
619 
620     activeView()->searchForward();
621 }
622 
623 void CrawlerWidget::searchBackward()
624 {
625     LOG(logDEBUG) << "CrawlerWidget::searchBackward";
626 
627     activeView()->searchBackward();
628 }
629 
630 void CrawlerWidget::searchRefreshChangedHandler( int state )
631 {
632     searchState_.setAutorefresh( state == Qt::Checked );
633     printSearchInfoMessage( logFilteredData_->getNbMatches() );
634 }
635 
636 void CrawlerWidget::searchTextChangeHandler()
637 {
638     // We suspend auto-refresh
639     searchState_.changeExpression();
640     printSearchInfoMessage( logFilteredData_->getNbMatches() );
641 }
642 
643 void CrawlerWidget::changeFilteredViewVisibility( int index )
644 {
645     QStandardItem* item = visibilityModel_->item( index );
646     FilteredView::Visibility visibility =
647         static_cast< FilteredView::Visibility>( item->data().toInt() );
648 
649     filteredView->setVisibility( visibility );
650 }
651 
652 void CrawlerWidget::addToSearch( const QString& string )
653 {
654     QString text = searchLineEdit->currentText();
655 
656     if ( text.isEmpty() )
657         text = string;
658     else {
659         // Escape the regexp chars from the string before adding it.
660         text += ( '|' + QRegExp::escape( string ) );
661     }
662 
663     searchLineEdit->setEditText( text );
664 
665     // Set the focus to lineEdit so that the user can press 'Return' immediately
666     searchLineEdit->lineEdit()->setFocus();
667 }
668 
669 void CrawlerWidget::mouseHoveredOverMatch( qint64 line )
670 {
671     qint64 line_in_mainview = logFilteredData_->getMatchingLineNumber( line );
672 
673     overviewWidget_->highlightLine( line_in_mainview );
674 }
675 
676 //
677 // Private functions
678 //
679 
680 // Create a new search using the text passed, replace the currently
681 // used one and destroy the old one.
682 void CrawlerWidget::replaceCurrentSearch( const QString& searchText )
683 {
684     // Interrupt the search if it's ongoing
685     logFilteredData_->interruptSearch();
686 
687     // We have to wait for the last search update (100%)
688     // before clearing/restarting to avoid having remaining results.
689 
690     // FIXME: this is a bit of a hack, we call processEvents
691     // for Qt to empty its event queue, including (hopefully)
692     // the search update event sent by logFilteredData_. It saves
693     // us the overhead of having proper sync.
694     QApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
695 
696     if ( !searchText.isEmpty() ) {
697         // Determine the type of regexp depending on the config
698         QRegExp::PatternSyntax syntax;
699         static Configuration& config = Persistent<Configuration>( "settings" );
700         switch ( config.mainRegexpType() ) {
701             case Wildcard:
702                 syntax = QRegExp::Wildcard;
703                 break;
704             case FixedString:
705                 syntax = QRegExp::FixedString;
706                 break;
707             default:
708                 syntax = QRegExp::RegExp2;
709                 break;
710         }
711 
712         // Set the pattern case insensitive if needed
713         Qt::CaseSensitivity case_sensitivity = Qt::CaseSensitive;
714         if ( ignoreCaseCheck->checkState() == Qt::Checked )
715             case_sensitivity = Qt::CaseInsensitive;
716 
717         // Constructs the regexp
718         QRegExp regexp( searchText, case_sensitivity, syntax );
719 
720         if ( regexp.isValid() ) {
721             // Activate the stop button
722             stopButton->setEnabled( true );
723             // Start a new asynchronous search
724             logFilteredData_->runSearch( regexp );
725             // Accept auto-refresh of the search
726             searchState_.startSearch();
727         }
728         else {
729             // The regexp is wrong
730             logFilteredData_->clearSearch();
731             filteredView->updateData();
732             searchState_.resetState();
733 
734             // Inform the user
735             QString errorMessage = tr("Error in expression: ");
736             errorMessage += regexp.errorString();
737             searchInfoLine->setPalette( errorPalette );
738             searchInfoLine->setText( errorMessage );
739         }
740     }
741     else {
742         logFilteredData_->clearSearch();
743         filteredView->updateData();
744         searchState_.resetState();
745         printSearchInfoMessage();
746     }
747     // Connect the search to the top view
748     logMainView->useNewFiltering( logFilteredData_ );
749 }
750 
751 // Updates the content of the drop down list for the saved searches,
752 // called when the SavedSearch has been changed.
753 void CrawlerWidget::updateSearchCombo()
754 {
755     const QString text = searchLineEdit->lineEdit()->text();
756     searchLineEdit->clear();
757     searchLineEdit->addItems( savedSearches->recentSearches() );
758     // In case we had something that wasn't added to the list (blank...):
759     searchLineEdit->lineEdit()->setText( text );
760 }
761 
762 // Print the search info message.
763 void CrawlerWidget::printSearchInfoMessage( int nbMatches )
764 {
765     QString text;
766 
767     switch ( searchState_.getState() ) {
768         case SearchState::NoSearch:
769             // Blank text is fine
770             break;
771         case SearchState::Static:
772             text = tr("%1 match%2 found.").arg( nbMatches )
773                 .arg( nbMatches > 1 ? "es" : "" );
774             break;
775         case SearchState::Autorefreshing:
776             text = tr("%1 match%2 found. Search is auto-refreshing...").arg( nbMatches )
777                 .arg( nbMatches > 1 ? "es" : "" );
778             break;
779         case SearchState::FileTruncated:
780             text = tr("File truncated on disk, previous search results are not valid anymore.");
781             break;
782     }
783 
784     searchInfoLine->setPalette( searchInfoLineDefaultPalette );
785     searchInfoLine->setText( text );
786 }
787 
788 //
789 // SearchState implementation
790 //
791 void CrawlerWidget::SearchState::resetState()
792 {
793     state_ = NoSearch;
794 }
795 
796 void CrawlerWidget::SearchState::setAutorefresh( bool refresh )
797 {
798     autoRefreshRequested_ = refresh;
799 
800     if ( refresh ) {
801         if ( state_ == Static )
802             state_ = Autorefreshing;
803     }
804     else {
805         if ( state_ == Autorefreshing )
806             state_ = Static;
807     }
808 }
809 
810 void CrawlerWidget::SearchState::truncateFile()
811 {
812     state_ = FileTruncated;
813 }
814 
815 void CrawlerWidget::SearchState::changeExpression()
816 {
817     if ( state_ == Autorefreshing )
818         state_ = Static;
819 }
820 
821 void CrawlerWidget::SearchState::stopSearch()
822 {
823     if ( state_ == Autorefreshing )
824         state_ = Static;
825 }
826 
827 void CrawlerWidget::SearchState::startSearch()
828 {
829     if ( autoRefreshRequested_ )
830         state_ = Autorefreshing;
831     else
832         state_ = Static;
833 }
834