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