xref: /glogg/src/crawlerwidget.cpp (revision b423cd88e57c52bc4c23c685b57ee676012b10f4)
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 
65 // The top line is first one on the main display
66 int CrawlerWidget::getTopLine() const
67 {
68     return logMainView->getTopLine();
69 }
70 
71 QString CrawlerWidget::getSelectedText() const
72 {
73     if ( filteredView->hasFocus() )
74         return filteredView->getSelection();
75     else
76         return logMainView->getSelection();
77 }
78 
79 void CrawlerWidget::selectAll()
80 {
81     activeView()->selectAll();
82 }
83 
84 // Return a pointer to the view in which we should do the QuickFind
85 SearchableWidgetInterface* CrawlerWidget::doGetActiveSearchable() const
86 {
87     return activeView();
88 }
89 
90 // Return all the searchable widgets (views)
91 std::vector<QObject*> CrawlerWidget::doGetAllSearchables() const
92 {
93     std::vector<QObject*> searchables =
94     { logMainView, filteredView };
95 
96     return searchables;
97 }
98 
99 //
100 // Public slots
101 //
102 
103 void CrawlerWidget::stopLoading()
104 {
105     logFilteredData_->interruptSearch();
106     logData_->interruptLoading();
107 }
108 
109 void CrawlerWidget::reload()
110 {
111     searchState_.resetState();
112     logFilteredData_->clearSearch();
113     filteredView->updateData();
114     printSearchInfoMessage();
115 
116     logData_->reload();
117 }
118 
119 //
120 // Protected functions
121 //
122 void CrawlerWidget::doSetData(
123         std::shared_ptr<LogData> log_data,
124         std::shared_ptr<LogFilteredData> filtered_data )
125 {
126     logData_         = log_data.get();
127     logFilteredData_ = filtered_data.get();
128 }
129 
130 void CrawlerWidget::doSetQuickFindPattern(
131         std::shared_ptr<QuickFindPattern> qfp )
132 {
133     quickFindPattern_ = qfp;
134 }
135 
136 void CrawlerWidget::doSetSavedSearches(
137         std::shared_ptr<SavedSearches> saved_searches )
138 {
139     savedSearches_ = saved_searches;
140 
141     // We do setup now, assuming doSetData has been called before
142     // us, that's not great really...
143     setup();
144 }
145 
146 //
147 // Events handlers
148 //
149 
150 void CrawlerWidget::keyPressEvent( QKeyEvent* keyEvent )
151 {
152     LOG(logDEBUG4) << "keyPressEvent received";
153 
154     switch ( (keyEvent->text())[0].toLatin1() ) {
155         case '/':
156             // displayQuickFindBar( QuickFindMux::Forward );
157             break;
158         case '?':
159             // displayQuickFindBar( QuickFindMux::Backward );
160             break;
161         default:
162             keyEvent->ignore();
163     }
164 
165     if ( !keyEvent->isAccepted() )
166         QSplitter::keyPressEvent( keyEvent );
167 }
168 
169 //
170 // Slots
171 //
172 
173 void CrawlerWidget::startNewSearch()
174 {
175     // Record the search line in the recent list
176     // (reload the list first in case another glogg changed it)
177     GetPersistentInfo().retrieve( "savedSearches" );
178     savedSearches_->addRecent( searchLineEdit->currentText() );
179     GetPersistentInfo().save( "savedSearches" );
180 
181     // Update the SearchLine (history)
182     updateSearchCombo();
183     // Call the private function to do the search
184     replaceCurrentSearch( searchLineEdit->currentText() );
185 }
186 
187 void CrawlerWidget::stopSearch()
188 {
189     logFilteredData_->interruptSearch();
190     searchState_.stopSearch();
191     printSearchInfoMessage();
192 }
193 
194 // When receiving the 'newDataAvailable' signal from LogFilteredData
195 void CrawlerWidget::updateFilteredView( int nbMatches, int progress )
196 {
197     LOG(logDEBUG) << "updateFilteredView received.";
198 
199     if ( progress == 100 ) {
200         // Searching done
201         printSearchInfoMessage( nbMatches );
202         searchInfoLine->hideGauge();
203         // De-activate the stop button
204         stopButton->setEnabled( false );
205     }
206     else {
207         // Search in progress
208         // We ignore 0% and 100% to avoid a flash when the search is very short
209         if ( progress > 0 ) {
210             searchInfoLine->setText(
211                     tr("Search in progress (%1 %)... %2 match%3 found so far.")
212                     .arg( progress )
213                     .arg( nbMatches )
214                     .arg( nbMatches > 1 ? "es" : "" ) );
215             searchInfoLine->displayGauge( progress );
216         }
217     }
218 
219     // Recompute the content of the filtered window.
220     filteredView->updateData();
221 
222     // Update the match overview
223     overview_->updateData( logData_->getNbLine() );
224 
225     // Also update the top window for the coloured bullets.
226     update();
227 }
228 
229 void CrawlerWidget::jumpToMatchingLine(int filteredLineNb)
230 {
231     int mainViewLine = logFilteredData_->getMatchingLineNumber(filteredLineNb);
232     logMainView->selectAndDisplayLine(mainViewLine);  // FIXME: should be done with a signal.
233 }
234 
235 void CrawlerWidget::markLineFromMain( qint64 line )
236 {
237     if ( logFilteredData_->isLineMarked( line ) )
238         logFilteredData_->deleteMark( line );
239     else
240         logFilteredData_->addMark( line );
241 
242     // Recompute the content of the filtered window.
243     filteredView->updateData();
244 
245     // Update the match overview
246     overview_->updateData( logData_->getNbLine() );
247 
248     // Also update the top window for the coloured bullets.
249     update();
250 }
251 
252 void CrawlerWidget::markLineFromFiltered( qint64 line )
253 {
254     qint64 line_in_file = logFilteredData_->getMatchingLineNumber( line );
255     if ( logFilteredData_->filteredLineTypeByIndex( line )
256             == LogFilteredData::Mark )
257         logFilteredData_->deleteMark( line_in_file );
258     else
259         logFilteredData_->addMark( line_in_file );
260 
261     // Recompute the content of the filtered window.
262     filteredView->updateData();
263 
264     // Update the match overview
265     overview_->updateData( logData_->getNbLine() );
266 
267     // Also update the top window for the coloured bullets.
268     update();
269 }
270 
271 void CrawlerWidget::applyConfiguration()
272 {
273     Configuration& config = Persistent<Configuration>( "settings" );
274     QFont font = config.mainFont();
275 
276     LOG(logDEBUG) << "CrawlerWidget::applyConfiguration";
277 
278     // Whatever font we use, we should NOT use kerning
279     font.setKerning( false );
280     font.setFixedPitch( true );
281 #if QT_VERSION > 0x040700
282     // Necessary on systems doing subpixel positionning (e.g. Ubuntu 12.04)
283     font.setStyleStrategy( QFont::ForceIntegerMetrics );
284 #endif
285     logMainView->setFont(font);
286     filteredView->setFont(font);
287 
288     logMainView->setLineNumbersVisible( config.mainLineNumbersVisible() );
289     filteredView->setLineNumbersVisible( config.filteredLineNumbersVisible() );
290 
291     overview_->setVisible( config.isOverviewVisible() );
292     logMainView->refreshOverview();
293 
294     logMainView->updateDisplaySize();
295     logMainView->update();
296     filteredView->updateDisplaySize();
297     filteredView->update();
298 
299     // Update the SearchLine (history)
300     updateSearchCombo();
301 }
302 
303 void CrawlerWidget::loadingFinishedHandler( bool success )
304 {
305     // We need to refresh the main window because the view lines on the
306     // overview have probably changed.
307     overview_->updateData( logData_->getNbLine() );
308 
309     // FIXME, handle topLine
310     // logMainView->updateData( logData_, topLine );
311     logMainView->updateData();
312 
313         // Shall we Forbid starting a search when loading in progress?
314         // searchButton->setEnabled( false );
315 
316     // searchButton->setEnabled( true );
317 
318     // See if we need to auto-refresh the search
319     if ( searchState_.isAutorefreshAllowed() ) {
320         LOG(logDEBUG) << "Refreshing the search";
321         logFilteredData_->updateSearch();
322     }
323 
324     emit loadingFinished( success );
325 }
326 
327 void CrawlerWidget::fileChangedHandler( LogData::MonitoredFileStatus status )
328 {
329     // Handle the case where the file has been truncated
330     if ( status == LogData::Truncated ) {
331         // Clear all marks (TODO offer the option to keep them)
332         logFilteredData_->clearMarks();
333         if ( ! searchInfoLine->text().isEmpty() ) {
334             // Invalidate the search
335             logFilteredData_->clearSearch();
336             filteredView->updateData();
337             searchState_.truncateFile();
338             printSearchInfoMessage();
339         }
340     }
341 }
342 
343 void CrawlerWidget::hideQuickFindBar()
344 {
345     // Restore the focus once the QFBar has been hidden
346 #if 0
347     qfSavedFocus_->setFocus();
348 #endif
349 }
350 
351 void CrawlerWidget::changeQFPattern( const QString& newPattern )
352 {
353     // quickFindWidget_->changeDisplayedPattern( newPattern );
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 = nullptr; // 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, SIGNAL( updateLineNumber( 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