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