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