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