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