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