xref: /glogg/src/crawlerwidget.cpp (revision 7552c461e028a0f633c5eebbcb1a22a5ac240290) !
1 /*
2  * Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 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 <QStandardItemModel>
35 #include <QHeaderView>
36 #include <QListView>
37 
38 #include "crawlerwidget.h"
39 
40 #include "quickfindpattern.h"
41 #include "overview.h"
42 #include "infoline.h"
43 #include "savedsearches.h"
44 #include "quickfindwidget.h"
45 #include "persistentinfo.h"
46 #include "configuration.h"
47 
48 // Palette for error signaling (yellow background)
49 const QPalette CrawlerWidget::errorPalette( QColor( "yellow" ) );
50 
51 // Implementation of the view context for the CrawlerWidget
52 class CrawlerWidgetContext : public ViewContextInterface {
53   public:
54     // Construct from the stored string representation
55     CrawlerWidgetContext( const char* string );
56     // Construct from the value passsed
57     CrawlerWidgetContext( QList<int> sizes,
58            bool ignore_case,
59            bool auto_refresh )
60         : sizes_( sizes ),
61           ignore_case_( ignore_case ),
62           auto_refresh_( auto_refresh ) {}
63 
64     // Implementation of the ViewContextInterface function
65     std::string toString() const;
66 
67     // Access the Qt sizes array for the QSplitter
68     QList<int> sizes() const { return sizes_; }
69 
70     bool ignoreCase() const { return ignore_case_; }
71     bool autoRefresh() const { return auto_refresh_; }
72 
73   private:
74     QList<int> sizes_;
75 
76     bool ignore_case_;
77     bool auto_refresh_;
78 };
79 
80 // Constructor only does trivial construction. The real work is done once
81 // the data is attached.
82 CrawlerWidget::CrawlerWidget( QWidget *parent )
83         : QSplitter( parent ), overview_()
84 {
85     logData_         = nullptr;
86     logFilteredData_ = nullptr;
87 
88     quickFindPattern_ = nullptr;
89     savedSearches_   = nullptr;
90     qfSavedFocus_    = nullptr;
91 
92     // Until we have received confirmation loading is finished, we
93     // should consider we are loading something.
94     loadingInProgress_ = true;
95     // and it's the first time
96     firstLoadDone_     = false;
97     nbMatches_         = 0;
98     dataStatus_        = DataStatus::OLD_DATA;
99 
100     currentLineNumber_ = 0;
101 }
102 
103 // The top line is first one on the main display
104 int CrawlerWidget::getTopLine() const
105 {
106     return logMainView->getTopLine();
107 }
108 
109 QString CrawlerWidget::getSelectedText() const
110 {
111     if ( filteredView->hasFocus() )
112         return filteredView->getSelection();
113     else
114         return logMainView->getSelection();
115 }
116 
117 void CrawlerWidget::selectAll()
118 {
119     activeView()->selectAll();
120 }
121 
122 CrawlerWidget::Encoding CrawlerWidget::encodingSetting() const
123 {
124     return encodingSetting_;
125 }
126 
127 bool CrawlerWidget::isFollowEnabled() const
128 {
129     return logMainView->isFollowEnabled();
130 }
131 
132 QString CrawlerWidget::encodingText() const
133 {
134     return encoding_text_;
135 }
136 
137 // Return a pointer to the view in which we should do the QuickFind
138 SearchableWidgetInterface* CrawlerWidget::doGetActiveSearchable() const
139 {
140     return activeView();
141 }
142 
143 // Return all the searchable widgets (views)
144 std::vector<QObject*> CrawlerWidget::doGetAllSearchables() const
145 {
146     std::vector<QObject*> searchables =
147     { logMainView, filteredView };
148 
149     return searchables;
150 }
151 
152 // Update the state of the parent
153 void CrawlerWidget::doSendAllStateSignals()
154 {
155     emit updateLineNumber( currentLineNumber_ );
156     if ( !loadingInProgress_ )
157         emit loadingFinished( LoadingStatus::Successful );
158 }
159 
160 //
161 // Public slots
162 //
163 
164 void CrawlerWidget::stopLoading()
165 {
166     logFilteredData_->interruptSearch();
167     logData_->interruptLoading();
168 }
169 
170 void CrawlerWidget::reload()
171 {
172     searchState_.resetState();
173     logFilteredData_->clearSearch();
174     filteredView->updateData();
175     printSearchInfoMessage();
176 
177     logData_->reload();
178 
179     // A reload is considered as a first load,
180     // this is to prevent the "new data" icon to be triggered.
181     firstLoadDone_ = false;
182 }
183 
184 void CrawlerWidget::setEncoding( Encoding encoding )
185 {
186     encodingSetting_ = encoding;
187     updateEncoding();
188 
189     update();
190 }
191 
192 //
193 // Protected functions
194 //
195 void CrawlerWidget::doSetData(
196         std::shared_ptr<LogData> log_data,
197         std::shared_ptr<LogFilteredData> filtered_data )
198 {
199     logData_         = log_data.get();
200     logFilteredData_ = filtered_data.get();
201 }
202 
203 void CrawlerWidget::doSetQuickFindPattern(
204         std::shared_ptr<QuickFindPattern> qfp )
205 {
206     quickFindPattern_ = qfp;
207 }
208 
209 void CrawlerWidget::doSetSavedSearches(
210         std::shared_ptr<SavedSearches> saved_searches )
211 {
212     savedSearches_ = saved_searches;
213 
214     // We do setup now, assuming doSetData has been called before
215     // us, that's not great really...
216     setup();
217 }
218 
219 void CrawlerWidget::doSetViewContext(
220         const char* view_context )
221 {
222     LOG(logDEBUG) << "CrawlerWidget::doSetViewContext: " << view_context;
223 
224     CrawlerWidgetContext context = { view_context };
225 
226     setSizes( context.sizes() );
227     ignoreCaseCheck->setCheckState( context.ignoreCase() ? Qt::Checked : Qt::Unchecked );
228 
229     auto auto_refresh_check_state = context.autoRefresh() ? Qt::Checked : Qt::Unchecked;
230     searchRefreshCheck->setCheckState( auto_refresh_check_state );
231     // Manually call the handler as it is not called when changing the state programmatically
232     searchRefreshChangedHandler( auto_refresh_check_state );
233 }
234 
235 std::shared_ptr<const ViewContextInterface>
236 CrawlerWidget::doGetViewContext() const
237 {
238     auto context = std::make_shared<const CrawlerWidgetContext>(
239             sizes(),
240             ( ignoreCaseCheck->checkState() == Qt::Checked ),
241             ( searchRefreshCheck->checkState() == Qt::Checked ) );
242 
243     return static_cast<std::shared_ptr<const ViewContextInterface>>( context );
244 }
245 
246 //
247 // Slots
248 //
249 
250 void CrawlerWidget::startNewSearch()
251 {
252     // Record the search line in the recent list
253     // (reload the list first in case another glogg changed it)
254     GetPersistentInfo().retrieve( "savedSearches" );
255     savedSearches_->addRecent( searchLineEdit->currentText() );
256     GetPersistentInfo().save( "savedSearches" );
257 
258     // Update the SearchLine (history)
259     updateSearchCombo();
260     // Call the private function to do the search
261     replaceCurrentSearch( searchLineEdit->currentText() );
262 }
263 
264 void CrawlerWidget::stopSearch()
265 {
266     logFilteredData_->interruptSearch();
267     searchState_.stopSearch();
268     printSearchInfoMessage();
269 }
270 
271 // When receiving the 'newDataAvailable' signal from LogFilteredData
272 void CrawlerWidget::updateFilteredView( int nbMatches, int progress )
273 {
274     LOG(logDEBUG) << "updateFilteredView received.";
275 
276     if ( progress == 100 ) {
277         // Searching done
278         printSearchInfoMessage( nbMatches );
279         searchInfoLine->hideGauge();
280         // De-activate the stop button
281         stopButton->setEnabled( false );
282     }
283     else {
284         // Search in progress
285         // We ignore 0% and 100% to avoid a flash when the search is very short
286         if ( progress > 0 ) {
287             searchInfoLine->setText(
288                     tr("Search in progress (%1 %)... %2 match%3 found so far.")
289                     .arg( progress )
290                     .arg( nbMatches )
291                     .arg( nbMatches > 1 ? "es" : "" ) );
292             searchInfoLine->displayGauge( progress );
293         }
294     }
295 
296     // If more (or less, e.g. come back to 0) matches have been found
297     if ( nbMatches != nbMatches_ ) {
298         nbMatches_ = nbMatches;
299 
300         // Recompute the content of the filtered window.
301         filteredView->updateData();
302 
303         // Update the match overview
304         overview_.updateData( logData_->getNbLine() );
305 
306         // New data found icon
307         changeDataStatus( DataStatus::NEW_FILTERED_DATA );
308 
309         // Also update the top window for the coloured bullets.
310         update();
311     }
312 }
313 
314 void CrawlerWidget::jumpToMatchingLine(int filteredLineNb)
315 {
316     int mainViewLine = logFilteredData_->getMatchingLineNumber(filteredLineNb);
317     logMainView->selectAndDisplayLine(mainViewLine);  // FIXME: should be done with a signal.
318 }
319 
320 void CrawlerWidget::updateLineNumberHandler( int line )
321 {
322     currentLineNumber_ = line;
323     emit updateLineNumber( line );
324 }
325 
326 void CrawlerWidget::markLineFromMain( qint64 line )
327 {
328     if ( line < logData_->getNbLine() ) {
329         if ( logFilteredData_->isLineMarked( line ) )
330             logFilteredData_->deleteMark( line );
331         else
332             logFilteredData_->addMark( line );
333 
334         // Recompute the content of both window.
335         filteredView->updateData();
336         logMainView->updateData();
337 
338         // Update the match overview
339         overview_.updateData( logData_->getNbLine() );
340 
341         // Also update the top window for the coloured bullets.
342         update();
343     }
344 }
345 
346 void CrawlerWidget::markLineFromFiltered( qint64 line )
347 {
348     if ( line < logFilteredData_->getNbLine() ) {
349         qint64 line_in_file = logFilteredData_->getMatchingLineNumber( line );
350         if ( logFilteredData_->filteredLineTypeByIndex( line )
351                 == LogFilteredData::Mark )
352             logFilteredData_->deleteMark( line_in_file );
353         else
354             logFilteredData_->addMark( line_in_file );
355 
356         // Recompute the content of both window.
357         filteredView->updateData();
358         logMainView->updateData();
359 
360         // Update the match overview
361         overview_.updateData( logData_->getNbLine() );
362 
363         // Also update the top window for the coloured bullets.
364         update();
365     }
366 }
367 
368 void CrawlerWidget::applyConfiguration()
369 {
370     std::shared_ptr<Configuration> config =
371         Persistent<Configuration>( "settings" );
372     QFont font = config->mainFont();
373 
374     LOG(logDEBUG) << "CrawlerWidget::applyConfiguration";
375 
376     // Whatever font we use, we should NOT use kerning
377     font.setKerning( false );
378     font.setFixedPitch( true );
379 #if QT_VERSION > 0x040700
380     // Necessary on systems doing subpixel positionning (e.g. Ubuntu 12.04)
381     font.setStyleStrategy( QFont::ForceIntegerMetrics );
382 #endif
383     logMainView->setFont(font);
384     filteredView->setFont(font);
385 
386     logMainView->setLineNumbersVisible( config->mainLineNumbersVisible() );
387     filteredView->setLineNumbersVisible( config->filteredLineNumbersVisible() );
388 
389     overview_.setVisible( config->isOverviewVisible() );
390     logMainView->refreshOverview();
391 
392     logMainView->updateDisplaySize();
393     logMainView->update();
394     filteredView->updateDisplaySize();
395     filteredView->update();
396 
397     // Polling interval
398     logData_->setPollingInterval(
399             config->pollingEnabled() ? config->pollIntervalMs() : 0 );
400 
401     // Update the SearchLine (history)
402     updateSearchCombo();
403 }
404 
405 void CrawlerWidget::enteringQuickFind()
406 {
407     LOG(logDEBUG) << "CrawlerWidget::enteringQuickFind";
408 
409     // Remember who had the focus (only if it is one of our views)
410     QWidget* focus_widget =  QApplication::focusWidget();
411 
412     if ( ( focus_widget == logMainView ) || ( focus_widget == filteredView ) )
413         qfSavedFocus_ = focus_widget;
414     else
415         qfSavedFocus_ = nullptr;
416 }
417 
418 void CrawlerWidget::exitingQuickFind()
419 {
420     // Restore the focus once the QFBar has been hidden
421     if ( qfSavedFocus_ )
422         qfSavedFocus_->setFocus();
423 }
424 
425 void CrawlerWidget::loadingFinishedHandler( LoadingStatus status )
426 {
427     loadingInProgress_ = false;
428 
429     // We need to refresh the main window because the view lines on the
430     // overview have probably changed.
431     overview_.updateData( logData_->getNbLine() );
432 
433     // FIXME, handle topLine
434     // logMainView->updateData( logData_, topLine );
435     logMainView->updateData();
436 
437         // Shall we Forbid starting a search when loading in progress?
438         // searchButton->setEnabled( false );
439 
440     // searchButton->setEnabled( true );
441 
442     // See if we need to auto-refresh the search
443     if ( searchState_.isAutorefreshAllowed() ) {
444         if ( searchState_.isFileTruncated() )
445             // We need to restart the search
446             replaceCurrentSearch( searchLineEdit->currentText() );
447         else
448             logFilteredData_->updateSearch();
449     }
450 
451     // Set the encoding for the views
452     updateEncoding();
453 
454     emit loadingFinished( status );
455 
456     // Also change the data available icon
457     if ( firstLoadDone_ )
458         changeDataStatus( DataStatus::NEW_DATA );
459     else
460         firstLoadDone_ = true;
461 }
462 
463 void CrawlerWidget::fileChangedHandler( LogData::MonitoredFileStatus status )
464 {
465     // Handle the case where the file has been truncated
466     if ( status == LogData::Truncated ) {
467         // Clear all marks (TODO offer the option to keep them)
468         logFilteredData_->clearMarks();
469         if ( ! searchInfoLine->text().isEmpty() ) {
470             // Invalidate the search
471             logFilteredData_->clearSearch();
472             filteredView->updateData();
473             searchState_.truncateFile();
474             printSearchInfoMessage();
475             nbMatches_ = 0;
476         }
477     }
478 }
479 
480 // Returns a pointer to the window in which the search should be done
481 AbstractLogView* CrawlerWidget::activeView() const
482 {
483     QWidget* activeView;
484 
485     // Search in the window that has focus, or the window where 'Find' was
486     // called from, or the main window.
487     if ( filteredView->hasFocus() || logMainView->hasFocus() )
488         activeView = QApplication::focusWidget();
489     else
490         activeView = qfSavedFocus_;
491 
492     if ( activeView ) {
493         AbstractLogView* view = qobject_cast<AbstractLogView*>( activeView );
494         return view;
495     }
496     else {
497         LOG(logWARNING) << "No active view, defaulting to logMainView";
498         return logMainView;
499     }
500 }
501 
502 void CrawlerWidget::searchForward()
503 {
504     LOG(logDEBUG) << "CrawlerWidget::searchForward";
505 
506     activeView()->searchForward();
507 }
508 
509 void CrawlerWidget::searchBackward()
510 {
511     LOG(logDEBUG) << "CrawlerWidget::searchBackward";
512 
513     activeView()->searchBackward();
514 }
515 
516 void CrawlerWidget::searchRefreshChangedHandler( int state )
517 {
518     searchState_.setAutorefresh( state == Qt::Checked );
519     printSearchInfoMessage( logFilteredData_->getNbMatches() );
520 }
521 
522 void CrawlerWidget::searchTextChangeHandler()
523 {
524     // We suspend auto-refresh
525     searchState_.changeExpression();
526     printSearchInfoMessage( logFilteredData_->getNbMatches() );
527 }
528 
529 void CrawlerWidget::changeFilteredViewVisibility( int index )
530 {
531     QStandardItem* item = visibilityModel_->item( index );
532     FilteredView::Visibility visibility =
533         static_cast< FilteredView::Visibility>( item->data().toInt() );
534 
535     filteredView->setVisibility( visibility );
536 }
537 
538 void CrawlerWidget::addToSearch( const QString& string )
539 {
540     QString text = searchLineEdit->currentText();
541 
542     if ( text.isEmpty() )
543         text = string;
544     else {
545         // Escape the regexp chars from the string before adding it.
546         text += ( '|' + QRegExp::escape( string ) );
547     }
548 
549     searchLineEdit->setEditText( text );
550 
551     // Set the focus to lineEdit so that the user can press 'Return' immediately
552     searchLineEdit->lineEdit()->setFocus();
553 }
554 
555 void CrawlerWidget::mouseHoveredOverMatch( qint64 line )
556 {
557     qint64 line_in_mainview = logFilteredData_->getMatchingLineNumber( line );
558 
559     overviewWidget_->highlightLine( line_in_mainview );
560 }
561 
562 void CrawlerWidget::activityDetected()
563 {
564     changeDataStatus( DataStatus::OLD_DATA );
565 }
566 
567 //
568 // Private functions
569 //
570 
571 // Build the widget and connect all the signals, this must be done once
572 // the data are attached.
573 void CrawlerWidget::setup()
574 {
575     setOrientation(Qt::Vertical);
576 
577     assert( logData_ );
578     assert( logFilteredData_ );
579 
580     // The views
581     bottomWindow = new QWidget;
582     overviewWidget_ = new OverviewWidget();
583     logMainView     = new LogMainView(
584             logData_, quickFindPattern_.get(), &overview_, overviewWidget_ );
585     filteredView    = new FilteredView(
586             logFilteredData_, quickFindPattern_.get() );
587 
588     overviewWidget_->setOverview( &overview_ );
589     overviewWidget_->setParent( logMainView );
590 
591     // Connect the search to the top view
592     logMainView->useNewFiltering( logFilteredData_ );
593 
594     // Construct the visibility button
595     visibilityModel_ = new QStandardItemModel( this );
596 
597     QStandardItem *marksAndMatchesItem = new QStandardItem( tr( "Marks and matches" ) );
598     QPixmap marksAndMatchesPixmap( 16, 10 );
599     marksAndMatchesPixmap.fill( Qt::gray );
600     marksAndMatchesItem->setIcon( QIcon( marksAndMatchesPixmap ) );
601     marksAndMatchesItem->setData( FilteredView::MarksAndMatches );
602     visibilityModel_->appendRow( marksAndMatchesItem );
603 
604     QStandardItem *marksItem = new QStandardItem( tr( "Marks" ) );
605     QPixmap marksPixmap( 16, 10 );
606     marksPixmap.fill( Qt::blue );
607     marksItem->setIcon( QIcon( marksPixmap ) );
608     marksItem->setData( FilteredView::MarksOnly );
609     visibilityModel_->appendRow( marksItem );
610 
611     QStandardItem *matchesItem = new QStandardItem( tr( "Matches" ) );
612     QPixmap matchesPixmap( 16, 10 );
613     matchesPixmap.fill( Qt::red );
614     matchesItem->setIcon( QIcon( matchesPixmap ) );
615     matchesItem->setData( FilteredView::MatchesOnly );
616     visibilityModel_->appendRow( matchesItem );
617 
618     QListView *visibilityView = new QListView( this );
619     visibilityView->setMovement( QListView::Static );
620     visibilityView->setMinimumWidth( 170 ); // Only needed with custom style-sheet
621 
622     visibilityBox = new QComboBox();
623     visibilityBox->setModel( visibilityModel_ );
624     visibilityBox->setView( visibilityView );
625 
626     // Select "Marks and matches" by default (same default as the filtered view)
627     visibilityBox->setCurrentIndex( 0 );
628 
629     // TODO: Maybe there is some way to set the popup width to be
630     // sized-to-content (as it is when the stylesheet is not overriden) in the
631     // stylesheet as opposed to setting a hard min-width on the view above.
632     visibilityBox->setStyleSheet( " \
633         QComboBox:on {\
634             padding: 1px 2px 1px 6px;\
635             width: 19px;\
636         } \
637         QComboBox:!on {\
638             padding: 1px 2px 1px 7px;\
639             width: 19px;\
640             height: 16px;\
641             border: 1px solid gray;\
642         } \
643         QComboBox::drop-down::down-arrow {\
644             width: 0px;\
645             border-width: 0px;\
646         } \
647 " );
648 
649     // Construct the Search Info line
650     searchInfoLine = new InfoLine();
651     searchInfoLine->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
652     searchInfoLine->setLineWidth( 1 );
653     searchInfoLineDefaultPalette = searchInfoLine->palette();
654 
655     ignoreCaseCheck = new QCheckBox( "Ignore &case" );
656     searchRefreshCheck = new QCheckBox( "Auto-&refresh" );
657 
658     // Construct the Search line
659     searchLabel = new QLabel(tr("&Text: "));
660     searchLineEdit = new QComboBox;
661     searchLineEdit->setEditable( true );
662     searchLineEdit->setCompleter( 0 );
663     searchLineEdit->addItems( savedSearches_->recentSearches() );
664     searchLineEdit->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
665     searchLineEdit->setSizeAdjustPolicy( QComboBox::AdjustToMinimumContentsLengthWithIcon );
666 
667     searchLabel->setBuddy( searchLineEdit );
668 
669     searchButton = new QToolButton();
670     searchButton->setText( tr("&Search") );
671     searchButton->setAutoRaise( true );
672 
673     stopButton = new QToolButton();
674     stopButton->setIcon( QIcon(":/images/stop14.png") );
675     stopButton->setAutoRaise( true );
676     stopButton->setEnabled( false );
677 
678     QHBoxLayout* searchLineLayout = new QHBoxLayout;
679     searchLineLayout->addWidget(searchLabel);
680     searchLineLayout->addWidget(searchLineEdit);
681     searchLineLayout->addWidget(searchButton);
682     searchLineLayout->addWidget(stopButton);
683     searchLineLayout->setContentsMargins(6, 0, 6, 0);
684     stopButton->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
685     searchButton->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
686 
687     QHBoxLayout* searchInfoLineLayout = new QHBoxLayout;
688     searchInfoLineLayout->addWidget( visibilityBox );
689     searchInfoLineLayout->addWidget( searchInfoLine );
690     searchInfoLineLayout->addWidget( ignoreCaseCheck );
691     searchInfoLineLayout->addWidget( searchRefreshCheck );
692 
693     // Construct the bottom window
694     QVBoxLayout* bottomMainLayout = new QVBoxLayout;
695     bottomMainLayout->addLayout(searchLineLayout);
696     bottomMainLayout->addLayout(searchInfoLineLayout);
697     bottomMainLayout->addWidget(filteredView);
698     bottomMainLayout->setContentsMargins(2, 1, 2, 1);
699     bottomWindow->setLayout(bottomMainLayout);
700 
701     addWidget( logMainView );
702     addWidget( bottomWindow );
703 
704     // Default splitter position (usually overridden by the config file)
705     QList<int> splitterSizes;
706     splitterSizes += 400;
707     splitterSizes += 100;
708     setSizes( splitterSizes );
709 
710     // Default search checkboxes
711     auto config = Persistent<Configuration>( "settings" );
712     searchRefreshCheck->setCheckState( config->isSearchAutoRefreshDefault() ?
713             Qt::Checked : Qt::Unchecked );
714     // Manually call the handler as it is not called when changing the state programmatically
715     searchRefreshChangedHandler( searchRefreshCheck->checkState() );
716     ignoreCaseCheck->setCheckState( config->isSearchIgnoreCaseDefault() ?
717             Qt::Checked : Qt::Unchecked );
718 
719     // Connect the signals
720     connect(searchLineEdit->lineEdit(), SIGNAL( returnPressed() ),
721             searchButton, SIGNAL( clicked() ));
722     connect(searchLineEdit->lineEdit(), SIGNAL( textEdited( const QString& ) ),
723             this, SLOT( searchTextChangeHandler() ));
724     connect(searchButton, SIGNAL( clicked() ),
725             this, SLOT( startNewSearch() ) );
726     connect(stopButton, SIGNAL( clicked() ),
727             this, SLOT( stopSearch() ) );
728 
729     connect(visibilityBox, SIGNAL( currentIndexChanged( int ) ),
730             this, SLOT( changeFilteredViewVisibility( int ) ) );
731 
732     connect(logMainView, SIGNAL( newSelection( int ) ),
733             logMainView, SLOT( update() ) );
734     connect(filteredView, SIGNAL( newSelection( int ) ),
735             this, SLOT( jumpToMatchingLine( int ) ) );
736     connect(filteredView, SIGNAL( newSelection( int ) ),
737             filteredView, SLOT( update() ) );
738     connect(logMainView, SIGNAL( updateLineNumber( int ) ),
739             this, SLOT( updateLineNumberHandler( int ) ) );
740     connect(logMainView, SIGNAL( markLine( qint64 ) ),
741             this, SLOT( markLineFromMain( qint64 ) ) );
742     connect(filteredView, SIGNAL( markLine( qint64 ) ),
743             this, SLOT( markLineFromFiltered( qint64 ) ) );
744 
745     connect(logMainView, SIGNAL( addToSearch( const QString& ) ),
746             this, SLOT( addToSearch( const QString& ) ) );
747     connect(filteredView, SIGNAL( addToSearch( const QString& ) ),
748             this, SLOT( addToSearch( const QString& ) ) );
749 
750     connect(filteredView, SIGNAL( mouseHoveredOverLine( qint64 ) ),
751             this, SLOT( mouseHoveredOverMatch( qint64 ) ) );
752     connect(filteredView, SIGNAL( mouseLeftHoveringZone() ),
753             overviewWidget_, SLOT( removeHighlight() ) );
754 
755     // Follow option (up and down)
756     connect(this, SIGNAL( followSet( bool ) ),
757             logMainView, SLOT( followSet( bool ) ) );
758     connect(this, SIGNAL( followSet( bool ) ),
759             filteredView, SLOT( followSet( bool ) ) );
760     connect(logMainView, SIGNAL( followModeChanged( bool ) ),
761             this, SIGNAL( followModeChanged( bool ) ) );
762     connect(filteredView, SIGNAL( followModeChanged( bool ) ),
763             this, SIGNAL( followModeChanged( bool ) ) );
764 
765     // Detect activity in the views
766     connect(logMainView, SIGNAL( activity() ),
767             this, SLOT( activityDetected() ) );
768     connect(filteredView, SIGNAL( activity() ),
769             this, SLOT( activityDetected() ) );
770 
771     connect( logFilteredData_, SIGNAL( searchProgressed( int, int ) ),
772             this, SLOT( updateFilteredView( int, int ) ) );
773 
774     // Sent load file update to MainWindow (for status update)
775     connect( logData_, SIGNAL( loadingProgressed( int ) ),
776             this, SIGNAL( loadingProgressed( int ) ) );
777     connect( logData_, SIGNAL( loadingFinished( LoadingStatus ) ),
778             this, SLOT( loadingFinishedHandler( LoadingStatus ) ) );
779     connect( logData_, SIGNAL( fileChanged( LogData::MonitoredFileStatus ) ),
780             this, SLOT( fileChangedHandler( LogData::MonitoredFileStatus ) ) );
781 
782     // Search auto-refresh
783     connect( searchRefreshCheck, SIGNAL( stateChanged( int ) ),
784             this, SLOT( searchRefreshChangedHandler( int ) ) );
785 
786     // Advise the parent the checkboxes have been changed
787     // (for maintaining default config)
788     connect( searchRefreshCheck, SIGNAL( stateChanged( int ) ),
789             this, SIGNAL( searchRefreshChanged( int ) ) );
790     connect( ignoreCaseCheck, SIGNAL( stateChanged( int ) ),
791             this, SIGNAL( ignoreCaseChanged( int ) ) );
792 
793     // Switch between views
794     connect( logMainView, SIGNAL( exitView() ),
795             filteredView, SLOT( setFocus() ) );
796     connect( filteredView, SIGNAL( exitView() ),
797             logMainView, SLOT( setFocus() ) );
798 }
799 
800 // Create a new search using the text passed, replace the currently
801 // used one and destroy the old one.
802 void CrawlerWidget::replaceCurrentSearch( const QString& searchText )
803 {
804     // Interrupt the search if it's ongoing
805     logFilteredData_->interruptSearch();
806 
807     // We have to wait for the last search update (100%)
808     // before clearing/restarting to avoid having remaining results.
809 
810     // FIXME: this is a bit of a hack, we call processEvents
811     // for Qt to empty its event queue, including (hopefully)
812     // the search update event sent by logFilteredData_. It saves
813     // us the overhead of having proper sync.
814     QApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
815 
816     nbMatches_ = 0;
817 
818     // Clear and recompute the content of the filtered window.
819     logFilteredData_->clearSearch();
820     filteredView->updateData();
821 
822     // Update the match overview
823     overview_.updateData( logData_->getNbLine() );
824 
825     if ( !searchText.isEmpty() ) {
826         // Determine the type of regexp depending on the config
827         QRegExp::PatternSyntax syntax;
828         static std::shared_ptr<Configuration> config =
829             Persistent<Configuration>( "settings" );
830         switch ( config->mainRegexpType() ) {
831             case Wildcard:
832                 syntax = QRegExp::Wildcard;
833                 break;
834             case FixedString:
835                 syntax = QRegExp::FixedString;
836                 break;
837             default:
838                 syntax = QRegExp::RegExp2;
839                 break;
840         }
841 
842         // Set the pattern case insensitive if needed
843         Qt::CaseSensitivity case_sensitivity = Qt::CaseSensitive;
844         if ( ignoreCaseCheck->checkState() == Qt::Checked )
845             case_sensitivity = Qt::CaseInsensitive;
846 
847         // Constructs the regexp
848         QRegExp regexp( searchText, case_sensitivity, syntax );
849 
850         if ( regexp.isValid() ) {
851             // Activate the stop button
852             stopButton->setEnabled( true );
853             // Start a new asynchronous search
854             logFilteredData_->runSearch( regexp );
855             // Accept auto-refresh of the search
856             searchState_.startSearch();
857         }
858         else {
859             // The regexp is wrong
860             logFilteredData_->clearSearch();
861             filteredView->updateData();
862             searchState_.resetState();
863 
864             // Inform the user
865             QString errorMessage = tr("Error in expression: ");
866             errorMessage += regexp.errorString();
867             searchInfoLine->setPalette( errorPalette );
868             searchInfoLine->setText( errorMessage );
869         }
870     }
871     else {
872         searchState_.resetState();
873         printSearchInfoMessage();
874     }
875 }
876 
877 // Updates the content of the drop down list for the saved searches,
878 // called when the SavedSearch has been changed.
879 void CrawlerWidget::updateSearchCombo()
880 {
881     const QString text = searchLineEdit->lineEdit()->text();
882     searchLineEdit->clear();
883     searchLineEdit->addItems( savedSearches_->recentSearches() );
884     // In case we had something that wasn't added to the list (blank...):
885     searchLineEdit->lineEdit()->setText( text );
886 }
887 
888 // Print the search info message.
889 void CrawlerWidget::printSearchInfoMessage( int nbMatches )
890 {
891     QString text;
892 
893     switch ( searchState_.getState() ) {
894         case SearchState::NoSearch:
895             // Blank text is fine
896             break;
897         case SearchState::Static:
898             text = tr("%1 match%2 found.").arg( nbMatches )
899                 .arg( nbMatches > 1 ? "es" : "" );
900             break;
901         case SearchState::Autorefreshing:
902             text = tr("%1 match%2 found. Search is auto-refreshing...").arg( nbMatches )
903                 .arg( nbMatches > 1 ? "es" : "" );
904             break;
905         case SearchState::FileTruncated:
906         case SearchState::TruncatedAutorefreshing:
907             text = tr("File truncated on disk, previous search results are not valid anymore.");
908             break;
909     }
910 
911     searchInfoLine->setPalette( searchInfoLineDefaultPalette );
912     searchInfoLine->setText( text );
913 }
914 
915 // Change the data status and, if needed, advise upstream.
916 void CrawlerWidget::changeDataStatus( DataStatus status )
917 {
918     if ( ( status != dataStatus_ )
919             && (! ( dataStatus_ == DataStatus::NEW_FILTERED_DATA
920                     && status == DataStatus::NEW_DATA ) ) ) {
921         dataStatus_ = status;
922         emit dataStatusChanged( dataStatus_ );
923     }
924 }
925 
926 // Determine the right encoding and set the views.
927 void CrawlerWidget::updateEncoding()
928 {
929     static const char* latin1_encoding = "iso-8859-1";
930     static const char* utf8_encoding   = "utf-8";
931 
932     const char* encoding;
933 
934     switch ( encodingSetting_ ) {
935         case ENCODING_AUTO:
936             switch ( logData_->getDetectedEncoding() ) {
937                 case EncodingSpeculator::Encoding::ASCII7:
938                     encoding = latin1_encoding;
939                     encoding_text_ = tr( "US-ASCII" );
940                     break;
941                 case EncodingSpeculator::Encoding::ASCII8:
942                     encoding = latin1_encoding;
943                     encoding_text_ = tr( "ISO-8859-1" );
944                     break;
945                 case EncodingSpeculator::Encoding::UTF8:
946                     encoding = utf8_encoding;
947                     encoding_text_ = tr( "UTF-8" );
948                     break;
949             }
950             break;
951         case ENCODING_UTF8:
952             encoding = utf8_encoding;
953             encoding_text_ = tr( "Displayed as UTF-8" );
954             break;
955         case ENCODING_ISO_8859_1:
956         default:
957             encoding = latin1_encoding;
958             encoding_text_ = tr( "Displayed as ISO-8859-1" );
959             break;
960     }
961 
962     logData_->setDisplayEncoding( encoding );
963     logMainView->forceRefresh();
964     logFilteredData_->setDisplayEncoding( encoding );
965     filteredView->forceRefresh();
966 }
967 
968 //
969 // SearchState implementation
970 //
971 void CrawlerWidget::SearchState::resetState()
972 {
973     state_ = NoSearch;
974 }
975 
976 void CrawlerWidget::SearchState::setAutorefresh( bool refresh )
977 {
978     autoRefreshRequested_ = refresh;
979 
980     if ( refresh ) {
981         if ( state_ == Static )
982             state_ = Autorefreshing;
983         /*
984         else if ( state_ == FileTruncated )
985             state_ = TruncatedAutorefreshing;
986         */
987     }
988     else {
989         if ( state_ == Autorefreshing )
990             state_ = Static;
991         else if ( state_ == TruncatedAutorefreshing )
992             state_ = FileTruncated;
993     }
994 }
995 
996 void CrawlerWidget::SearchState::truncateFile()
997 {
998     if ( state_ == Autorefreshing || state_ == TruncatedAutorefreshing ) {
999         state_ = TruncatedAutorefreshing;
1000     }
1001     else {
1002         state_ = FileTruncated;
1003     }
1004 }
1005 
1006 void CrawlerWidget::SearchState::changeExpression()
1007 {
1008     if ( state_ == Autorefreshing )
1009         state_ = Static;
1010 }
1011 
1012 void CrawlerWidget::SearchState::stopSearch()
1013 {
1014     if ( state_ == Autorefreshing )
1015         state_ = Static;
1016 }
1017 
1018 void CrawlerWidget::SearchState::startSearch()
1019 {
1020     if ( autoRefreshRequested_ )
1021         state_ = Autorefreshing;
1022     else
1023         state_ = Static;
1024 }
1025 
1026 /*
1027  * CrawlerWidgetContext
1028  */
1029 CrawlerWidgetContext::CrawlerWidgetContext( const char* string )
1030 {
1031     QRegExp regex = QRegExp( "S(\\d+):(\\d+)" );
1032 
1033     if ( regex.indexIn( string ) > -1 ) {
1034         sizes_ = { regex.cap(1).toInt(), regex.cap(2).toInt() };
1035         LOG(logDEBUG) << "sizes_: " << sizes_[0] << " " << sizes_[1];
1036     }
1037     else {
1038         LOG(logWARNING) << "Unrecognised view size: " << string;
1039 
1040         // Default values;
1041         sizes_ = { 100, 400 };
1042     }
1043 
1044     QRegExp case_refresh_regex = QRegExp( "IC(\\d+):AR(\\d+)" );
1045 
1046     if ( case_refresh_regex.indexIn( string ) > -1 ) {
1047         ignore_case_ = ( case_refresh_regex.cap(1).toInt() == 1 );
1048         auto_refresh_ = ( case_refresh_regex.cap(2).toInt() == 1 );
1049 
1050         LOG(logDEBUG) << "ignore_case_: " << ignore_case_ << " auto_refresh_: "
1051             << auto_refresh_;
1052     }
1053     else {
1054         LOG(logWARNING) << "Unrecognised case/refresh: " << string;
1055         ignore_case_ = false;
1056         auto_refresh_ = false;
1057     }
1058 }
1059 
1060 std::string CrawlerWidgetContext::toString() const
1061 {
1062     char string[160];
1063 
1064     snprintf( string, sizeof string, "S%d:%d:IC%d:AR%d",
1065             sizes_[0], sizes_[1],
1066             ignore_case_, auto_refresh_ );
1067 
1068     return { string };
1069 }
1070