/*
* Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Nicolas Bonnefon and other contributors
*
* This file is part of glogg.
*
* glogg is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* glogg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with glogg. If not, see .
*/
// This file implements the CrawlerWidget class.
// It is responsible for creating and managing the two views and all
// the UI elements. It implements the connection between the UI elements.
// It also interacts with the sets of data (full and filtered).
#include "log.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "crawlerwidget.h"
#include "quickfindpattern.h"
#include "overview.h"
#include "infoline.h"
#include "savedsearches.h"
#include "quickfindwidget.h"
#include "persistentinfo.h"
#include "configuration.h"
// Palette for error signaling (yellow background)
const QPalette CrawlerWidget::errorPalette( QColor( "yellow" ) );
// Implementation of the view context for the CrawlerWidget
class CrawlerWidgetContext : public ViewContextInterface {
public:
// Construct from the stored string representation
CrawlerWidgetContext( const char* string );
// Construct from the value passsed
CrawlerWidgetContext( QList sizes,
bool ignore_case,
bool auto_refresh )
: sizes_( sizes ),
ignore_case_( ignore_case ),
auto_refresh_( auto_refresh ) {}
// Implementation of the ViewContextInterface function
std::string toString() const;
// Access the Qt sizes array for the QSplitter
QList sizes() const { return sizes_; }
bool ignoreCase() const { return ignore_case_; }
bool autoRefresh() const { return auto_refresh_; }
private:
QList sizes_;
bool ignore_case_;
bool auto_refresh_;
};
// Constructor only does trivial construction. The real work is done once
// the data is attached.
CrawlerWidget::CrawlerWidget( QWidget *parent )
: QSplitter( parent ), overview_()
{
logData_ = nullptr;
logFilteredData_ = nullptr;
quickFindPattern_ = nullptr;
savedSearches_ = nullptr;
qfSavedFocus_ = nullptr;
// Until we have received confirmation loading is finished, we
// should consider we are loading something.
loadingInProgress_ = true;
// and it's the first time
firstLoadDone_ = false;
nbMatches_ = 0;
dataStatus_ = DataStatus::OLD_DATA;
currentLineNumber_ = 0;
}
// The top line is first one on the main display
int CrawlerWidget::getTopLine() const
{
return logMainView->getTopLine();
}
QString CrawlerWidget::getSelectedText() const
{
if ( filteredView->hasFocus() )
return filteredView->getSelection();
else
return logMainView->getSelection();
}
void CrawlerWidget::selectAll()
{
activeView()->selectAll();
}
Encoding CrawlerWidget::encodingSetting() const
{
return encodingSetting_;
}
bool CrawlerWidget::isFollowEnabled() const
{
return logMainView->isFollowEnabled();
}
QString CrawlerWidget::encodingText() const
{
return encoding_text_;
}
// Return a pointer to the view in which we should do the QuickFind
SearchableWidgetInterface* CrawlerWidget::doGetActiveSearchable() const
{
return activeView();
}
// Return all the searchable widgets (views)
std::vector CrawlerWidget::doGetAllSearchables() const
{
std::vector searchables =
{ logMainView, filteredView };
return searchables;
}
// Update the state of the parent
void CrawlerWidget::doSendAllStateSignals()
{
emit updateLineNumber( currentLineNumber_ );
if ( !loadingInProgress_ )
emit loadingFinished( LoadingStatus::Successful );
}
void CrawlerWidget::keyPressEvent( QKeyEvent* keyEvent )
{
bool noModifier = keyEvent->modifiers() == Qt::NoModifier;
if ( keyEvent->key() == Qt::Key_V && noModifier )
visibilityBox->setCurrentIndex(
( visibilityBox->currentIndex() + 1 ) % visibilityBox->count() );
else {
const char character = (keyEvent->text())[0].toLatin1();
if ( character == '+' )
changeTopViewSize( 1 );
else if ( character == '-' )
changeTopViewSize( -1 );
else
QSplitter::keyPressEvent( keyEvent );
}
}
//
// Public slots
//
void CrawlerWidget::stopLoading()
{
logFilteredData_->interruptSearch();
logData_->interruptLoading();
}
void CrawlerWidget::reload()
{
searchState_.resetState();
logFilteredData_->clearSearch();
filteredView->updateData();
printSearchInfoMessage();
logData_->reload();
// A reload is considered as a first load,
// this is to prevent the "new data" icon to be triggered.
firstLoadDone_ = false;
}
void CrawlerWidget::setEncoding( Encoding encoding )
{
encodingSetting_ = encoding;
updateEncoding();
update();
}
//
// Protected functions
//
void CrawlerWidget::doSetData(
std::shared_ptr log_data,
std::shared_ptr filtered_data )
{
logData_ = log_data.get();
logFilteredData_ = filtered_data.get();
}
void CrawlerWidget::doSetQuickFindPattern(
std::shared_ptr qfp )
{
quickFindPattern_ = qfp;
}
void CrawlerWidget::doSetSavedSearches(
std::shared_ptr saved_searches )
{
savedSearches_ = saved_searches;
// We do setup now, assuming doSetData has been called before
// us, that's not great really...
setup();
}
void CrawlerWidget::doSetViewContext(
const char* view_context )
{
LOG(logDEBUG) << "CrawlerWidget::doSetViewContext: " << view_context;
CrawlerWidgetContext context = { view_context };
setSizes( context.sizes() );
ignoreCaseCheck->setCheckState( context.ignoreCase() ? Qt::Checked : Qt::Unchecked );
auto auto_refresh_check_state = context.autoRefresh() ? Qt::Checked : Qt::Unchecked;
searchRefreshCheck->setCheckState( auto_refresh_check_state );
// Manually call the handler as it is not called when changing the state programmatically
searchRefreshChangedHandler( auto_refresh_check_state );
}
std::shared_ptr
CrawlerWidget::doGetViewContext() const
{
auto context = std::make_shared(
sizes(),
( ignoreCaseCheck->checkState() == Qt::Checked ),
( searchRefreshCheck->checkState() == Qt::Checked ) );
return static_cast>( context );
}
//
// Slots
//
void CrawlerWidget::startNewSearch()
{
// Record the search line in the recent list
// (reload the list first in case another glogg changed it)
GetPersistentInfo().retrieve( "savedSearches" );
savedSearches_->addRecent( searchLineEdit->currentText() );
GetPersistentInfo().save( "savedSearches" );
// Update the SearchLine (history)
updateSearchCombo();
// Call the private function to do the search
replaceCurrentSearch( searchLineEdit->currentText() );
}
void CrawlerWidget::stopSearch()
{
logFilteredData_->interruptSearch();
searchState_.stopSearch();
printSearchInfoMessage();
}
// When receiving the 'newDataAvailable' signal from LogFilteredData
void CrawlerWidget::updateFilteredView( int nbMatches, int progress )
{
LOG(logDEBUG) << "updateFilteredView received.";
if ( progress == 100 ) {
// Searching done
printSearchInfoMessage( nbMatches );
searchInfoLine->hideGauge();
// De-activate the stop button
stopButton->setEnabled( false );
}
else {
// Search in progress
// We ignore 0% and 100% to avoid a flash when the search is very short
if ( progress > 0 ) {
searchInfoLine->setText(
tr("Search in progress (%1 %)... %2 match%3 found so far.")
.arg( progress )
.arg( nbMatches )
.arg( nbMatches > 1 ? "es" : "" ) );
searchInfoLine->displayGauge( progress );
}
}
// If more (or less, e.g. come back to 0) matches have been found
if ( nbMatches != nbMatches_ ) {
nbMatches_ = nbMatches;
// Recompute the content of the filtered window.
filteredView->updateData();
// Update the match overview
overview_.updateData( logData_->getNbLine() );
// New data found icon
changeDataStatus( DataStatus::NEW_FILTERED_DATA );
// Also update the top window for the coloured bullets.
update();
}
if ( progress == 100 ) {
const int currenLineIndex = logFilteredData_->getLineIndexNumber(currentLineNumber_);
LOG(logDEBUG) << "updateFilteredView: restoring selection: "
<< " absolute line number (0based) " << currentLineNumber_
<< " index " << currenLineIndex;
filteredView->selectAndDisplayLine(currenLineIndex);
}
}
void CrawlerWidget::jumpToMatchingLine(int filteredLineNb)
{
int mainViewLine = logFilteredData_->getMatchingLineNumber(filteredLineNb);
logMainView->selectAndDisplayLine(mainViewLine); // FIXME: should be done with a signal.
}
void CrawlerWidget::updateLineNumberHandler( int line )
{
currentLineNumber_ = line;
emit updateLineNumber( line );
}
void CrawlerWidget::markLineFromMain( qint64 line )
{
if ( line < logData_->getNbLine() ) {
if ( logFilteredData_->isLineMarked( line ) )
logFilteredData_->deleteMark( line );
else
logFilteredData_->addMark( line );
// Recompute the content of both window.
filteredView->updateData();
logMainView->updateData();
// Update the match overview
overview_.updateData( logData_->getNbLine() );
// Also update the top window for the coloured bullets.
update();
}
}
void CrawlerWidget::markLineFromFiltered( qint64 line )
{
if ( line < logFilteredData_->getNbLine() ) {
qint64 line_in_file = logFilteredData_->getMatchingLineNumber( line );
if ( logFilteredData_->filteredLineTypeByIndex( line )
== LogFilteredData::Mark )
logFilteredData_->deleteMark( line_in_file );
else
logFilteredData_->addMark( line_in_file );
// Recompute the content of both window.
filteredView->updateData();
logMainView->updateData();
// Update the match overview
overview_.updateData( logData_->getNbLine() );
// Also update the top window for the coloured bullets.
update();
}
}
void CrawlerWidget::applyConfiguration()
{
std::shared_ptr config =
Persistent( "settings" );
QFont font = config->mainFont();
LOG(logDEBUG) << "CrawlerWidget::applyConfiguration";
// Whatever font we use, we should NOT use kerning
font.setKerning( false );
font.setFixedPitch( true );
#if QT_VERSION > 0x040700
// Necessary on systems doing subpixel positionning (e.g. Ubuntu 12.04)
font.setStyleStrategy( QFont::ForceIntegerMetrics );
#endif
logMainView->setFont(font);
filteredView->setFont(font);
logMainView->setLineNumbersVisible( config->mainLineNumbersVisible() );
filteredView->setLineNumbersVisible( config->filteredLineNumbersVisible() );
overview_.setVisible( config->isOverviewVisible() );
logMainView->refreshOverview();
logMainView->updateDisplaySize();
logMainView->update();
filteredView->updateDisplaySize();
filteredView->update();
// Polling interval
logData_->setPollingInterval(
config->pollingEnabled() ? config->pollIntervalMs() : 0 );
// Update the SearchLine (history)
updateSearchCombo();
}
void CrawlerWidget::enteringQuickFind()
{
LOG(logDEBUG) << "CrawlerWidget::enteringQuickFind";
// Remember who had the focus (only if it is one of our views)
QWidget* focus_widget = QApplication::focusWidget();
if ( ( focus_widget == logMainView ) || ( focus_widget == filteredView ) )
qfSavedFocus_ = focus_widget;
else
qfSavedFocus_ = nullptr;
}
void CrawlerWidget::exitingQuickFind()
{
// Restore the focus once the QFBar has been hidden
if ( qfSavedFocus_ )
qfSavedFocus_->setFocus();
}
void CrawlerWidget::loadingFinishedHandler( LoadingStatus status )
{
loadingInProgress_ = false;
// We need to refresh the main window because the view lines on the
// overview have probably changed.
overview_.updateData( logData_->getNbLine() );
// FIXME, handle topLine
// logMainView->updateData( logData_, topLine );
logMainView->updateData();
// Shall we Forbid starting a search when loading in progress?
// searchButton->setEnabled( false );
// searchButton->setEnabled( true );
// See if we need to auto-refresh the search
if ( searchState_.isAutorefreshAllowed() ) {
if ( searchState_.isFileTruncated() )
// We need to restart the search
replaceCurrentSearch( searchLineEdit->currentText() );
else
logFilteredData_->updateSearch();
}
// Set the encoding for the views
updateEncoding();
emit loadingFinished( status );
// Also change the data available icon
if ( firstLoadDone_ )
changeDataStatus( DataStatus::NEW_DATA );
else
firstLoadDone_ = true;
}
void CrawlerWidget::fileChangedHandler( LogData::MonitoredFileStatus status )
{
// Handle the case where the file has been truncated
if ( status == LogData::Truncated ) {
// Clear all marks (TODO offer the option to keep them)
logFilteredData_->clearMarks();
if ( ! searchInfoLine->text().isEmpty() ) {
// Invalidate the search
logFilteredData_->clearSearch();
filteredView->updateData();
searchState_.truncateFile();
printSearchInfoMessage();
nbMatches_ = 0;
}
}
}
// Returns a pointer to the window in which the search should be done
AbstractLogView* CrawlerWidget::activeView() const
{
QWidget* activeView;
// Search in the window that has focus, or the window where 'Find' was
// called from, or the main window.
if ( filteredView->hasFocus() || logMainView->hasFocus() )
activeView = QApplication::focusWidget();
else
activeView = qfSavedFocus_;
if ( activeView ) {
AbstractLogView* view = qobject_cast( activeView );
return view;
}
else {
LOG(logWARNING) << "No active view, defaulting to logMainView";
return logMainView;
}
}
void CrawlerWidget::searchForward()
{
LOG(logDEBUG) << "CrawlerWidget::searchForward";
activeView()->searchForward();
}
void CrawlerWidget::searchBackward()
{
LOG(logDEBUG) << "CrawlerWidget::searchBackward";
activeView()->searchBackward();
}
void CrawlerWidget::searchRefreshChangedHandler( int state )
{
searchState_.setAutorefresh( state == Qt::Checked );
printSearchInfoMessage( logFilteredData_->getNbMatches() );
}
void CrawlerWidget::searchTextChangeHandler()
{
// We suspend auto-refresh
searchState_.changeExpression();
printSearchInfoMessage( logFilteredData_->getNbMatches() );
}
void CrawlerWidget::changeFilteredViewVisibility( int index )
{
QStandardItem* item = visibilityModel_->item( index );
FilteredView::Visibility visibility =
static_cast< FilteredView::Visibility>( item->data().toInt() );
filteredView->setVisibility( visibility );
const int lineIndex = logFilteredData_->getLineIndexNumber( currentLineNumber_ );
filteredView->selectAndDisplayLine( lineIndex );
}
void CrawlerWidget::addToSearch( const QString& string )
{
QString text = searchLineEdit->currentText();
if ( text.isEmpty() )
text = string;
else {
// Escape the regexp chars from the string before adding it.
text += ( '|' + QRegularExpression::escape( string ) );
}
searchLineEdit->setEditText( text );
// Set the focus to lineEdit so that the user can press 'Return' immediately
searchLineEdit->lineEdit()->setFocus();
}
void CrawlerWidget::mouseHoveredOverMatch( qint64 line )
{
qint64 line_in_mainview = logFilteredData_->getMatchingLineNumber( line );
overviewWidget_->highlightLine( line_in_mainview );
}
void CrawlerWidget::activityDetected()
{
changeDataStatus( DataStatus::OLD_DATA );
}
//
// Private functions
//
// Build the widget and connect all the signals, this must be done once
// the data are attached.
void CrawlerWidget::setup()
{
setOrientation(Qt::Vertical);
assert( logData_ );
assert( logFilteredData_ );
// The views
bottomWindow = new QWidget;
overviewWidget_ = new OverviewWidget();
logMainView = new LogMainView(
logData_, quickFindPattern_.get(), &overview_, overviewWidget_ );
filteredView = new FilteredView(
logFilteredData_, quickFindPattern_.get() );
overviewWidget_->setOverview( &overview_ );
overviewWidget_->setParent( logMainView );
// Connect the search to the top view
logMainView->useNewFiltering( logFilteredData_ );
// Construct the visibility button
visibilityModel_ = new QStandardItemModel( this );
QStandardItem *marksAndMatchesItem = new QStandardItem( tr( "Marks and matches" ) );
QPixmap marksAndMatchesPixmap( 16, 10 );
marksAndMatchesPixmap.fill( Qt::gray );
marksAndMatchesItem->setIcon( QIcon( marksAndMatchesPixmap ) );
marksAndMatchesItem->setData( FilteredView::MarksAndMatches );
visibilityModel_->appendRow( marksAndMatchesItem );
QStandardItem *marksItem = new QStandardItem( tr( "Marks" ) );
QPixmap marksPixmap( 16, 10 );
marksPixmap.fill( Qt::blue );
marksItem->setIcon( QIcon( marksPixmap ) );
marksItem->setData( FilteredView::MarksOnly );
visibilityModel_->appendRow( marksItem );
QStandardItem *matchesItem = new QStandardItem( tr( "Matches" ) );
QPixmap matchesPixmap( 16, 10 );
matchesPixmap.fill( Qt::red );
matchesItem->setIcon( QIcon( matchesPixmap ) );
matchesItem->setData( FilteredView::MatchesOnly );
visibilityModel_->appendRow( matchesItem );
QListView *visibilityView = new QListView( this );
visibilityView->setMovement( QListView::Static );
visibilityView->setMinimumWidth( 170 ); // Only needed with custom style-sheet
visibilityBox = new QComboBox();
visibilityBox->setModel( visibilityModel_ );
visibilityBox->setView( visibilityView );
// Select "Marks and matches" by default (same default as the filtered view)
visibilityBox->setCurrentIndex( 0 );
// TODO: Maybe there is some way to set the popup width to be
// sized-to-content (as it is when the stylesheet is not overriden) in the
// stylesheet as opposed to setting a hard min-width on the view above.
visibilityBox->setStyleSheet( " \
QComboBox:on {\
padding: 1px 2px 1px 6px;\
width: 19px;\
} \
QComboBox:!on {\
padding: 1px 2px 1px 7px;\
width: 19px;\
height: 16px;\
border: 1px solid gray;\
} \
QComboBox::drop-down::down-arrow {\
width: 0px;\
border-width: 0px;\
} \
" );
// Construct the Search Info line
searchInfoLine = new InfoLine();
searchInfoLine->setFrameStyle( QFrame::WinPanel | QFrame::Sunken );
searchInfoLine->setLineWidth( 1 );
searchInfoLineDefaultPalette = searchInfoLine->palette();
ignoreCaseCheck = new QCheckBox( "Ignore &case" );
searchRefreshCheck = new QCheckBox( "Auto-&refresh" );
// Construct the Search line
searchLabel = new QLabel(tr("&Text: "));
searchLineEdit = new QComboBox;
searchLineEdit->setEditable( true );
searchLineEdit->setCompleter( 0 );
searchLineEdit->addItems( savedSearches_->recentSearches() );
searchLineEdit->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Minimum );
searchLineEdit->setSizeAdjustPolicy( QComboBox::AdjustToMinimumContentsLengthWithIcon );
searchLabel->setBuddy( searchLineEdit );
searchButton = new QToolButton();
searchButton->setText( tr("&Search") );
searchButton->setAutoRaise( true );
stopButton = new QToolButton();
stopButton->setIcon( QIcon(":/images/stop14.png") );
stopButton->setAutoRaise( true );
stopButton->setEnabled( false );
QHBoxLayout* searchLineLayout = new QHBoxLayout;
searchLineLayout->addWidget(searchLabel);
searchLineLayout->addWidget(searchLineEdit);
searchLineLayout->addWidget(searchButton);
searchLineLayout->addWidget(stopButton);
searchLineLayout->setContentsMargins(6, 0, 6, 0);
stopButton->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
searchButton->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
QHBoxLayout* searchInfoLineLayout = new QHBoxLayout;
searchInfoLineLayout->addWidget( visibilityBox );
searchInfoLineLayout->addWidget( searchInfoLine );
searchInfoLineLayout->addWidget( ignoreCaseCheck );
searchInfoLineLayout->addWidget( searchRefreshCheck );
// Construct the bottom window
QVBoxLayout* bottomMainLayout = new QVBoxLayout;
bottomMainLayout->addLayout(searchLineLayout);
bottomMainLayout->addLayout(searchInfoLineLayout);
bottomMainLayout->addWidget(filteredView);
bottomMainLayout->setContentsMargins(2, 1, 2, 1);
bottomWindow->setLayout(bottomMainLayout);
addWidget( logMainView );
addWidget( bottomWindow );
// Default splitter position (usually overridden by the config file)
QList splitterSizes;
splitterSizes += 400;
splitterSizes += 100;
setSizes( splitterSizes );
// Default search checkboxes
auto config = Persistent( "settings" );
searchRefreshCheck->setCheckState( config->isSearchAutoRefreshDefault() ?
Qt::Checked : Qt::Unchecked );
// Manually call the handler as it is not called when changing the state programmatically
searchRefreshChangedHandler( searchRefreshCheck->checkState() );
ignoreCaseCheck->setCheckState( config->isSearchIgnoreCaseDefault() ?
Qt::Checked : Qt::Unchecked );
// Connect the signals
connect(searchLineEdit->lineEdit(), SIGNAL( returnPressed() ),
searchButton, SIGNAL( clicked() ));
connect(searchLineEdit->lineEdit(), SIGNAL( textEdited( const QString& ) ),
this, SLOT( searchTextChangeHandler() ));
connect(searchButton, SIGNAL( clicked() ),
this, SLOT( startNewSearch() ) );
connect(stopButton, SIGNAL( clicked() ),
this, SLOT( stopSearch() ) );
connect(visibilityBox, SIGNAL( currentIndexChanged( int ) ),
this, SLOT( changeFilteredViewVisibility( int ) ) );
connect(logMainView, SIGNAL( newSelection( int ) ),
logMainView, SLOT( update() ) );
connect(filteredView, SIGNAL( newSelection( int ) ),
this, SLOT( jumpToMatchingLine( int ) ) );
connect(filteredView, SIGNAL( newSelection( int ) ),
filteredView, SLOT( update() ) );
connect(logMainView, SIGNAL( updateLineNumber( int ) ),
this, SLOT( updateLineNumberHandler( int ) ) );
connect(logMainView, SIGNAL( markLine( qint64 ) ),
this, SLOT( markLineFromMain( qint64 ) ) );
connect(filteredView, SIGNAL( markLine( qint64 ) ),
this, SLOT( markLineFromFiltered( qint64 ) ) );
connect(logMainView, SIGNAL( addToSearch( const QString& ) ),
this, SLOT( addToSearch( const QString& ) ) );
connect(filteredView, SIGNAL( addToSearch( const QString& ) ),
this, SLOT( addToSearch( const QString& ) ) );
connect(filteredView, SIGNAL( mouseHoveredOverLine( qint64 ) ),
this, SLOT( mouseHoveredOverMatch( qint64 ) ) );
connect(filteredView, SIGNAL( mouseLeftHoveringZone() ),
overviewWidget_, SLOT( removeHighlight() ) );
// Follow option (up and down)
connect(this, SIGNAL( followSet( bool ) ),
logMainView, SLOT( followSet( bool ) ) );
connect(this, SIGNAL( followSet( bool ) ),
filteredView, SLOT( followSet( bool ) ) );
connect(logMainView, SIGNAL( followModeChanged( bool ) ),
this, SIGNAL( followModeChanged( bool ) ) );
connect(filteredView, SIGNAL( followModeChanged( bool ) ),
this, SIGNAL( followModeChanged( bool ) ) );
// Detect activity in the views
connect(logMainView, SIGNAL( activity() ),
this, SLOT( activityDetected() ) );
connect(filteredView, SIGNAL( activity() ),
this, SLOT( activityDetected() ) );
connect( logFilteredData_, SIGNAL( searchProgressed( int, int ) ),
this, SLOT( updateFilteredView( int, int ) ) );
// Sent load file update to MainWindow (for status update)
connect( logData_, SIGNAL( loadingProgressed( int ) ),
this, SIGNAL( loadingProgressed( int ) ) );
connect( logData_, SIGNAL( loadingFinished( LoadingStatus ) ),
this, SLOT( loadingFinishedHandler( LoadingStatus ) ) );
connect( logData_, SIGNAL( fileChanged( LogData::MonitoredFileStatus ) ),
this, SLOT( fileChangedHandler( LogData::MonitoredFileStatus ) ) );
// Search auto-refresh
connect( searchRefreshCheck, SIGNAL( stateChanged( int ) ),
this, SLOT( searchRefreshChangedHandler( int ) ) );
// Advise the parent the checkboxes have been changed
// (for maintaining default config)
connect( searchRefreshCheck, SIGNAL( stateChanged( int ) ),
this, SIGNAL( searchRefreshChanged( int ) ) );
connect( ignoreCaseCheck, SIGNAL( stateChanged( int ) ),
this, SIGNAL( ignoreCaseChanged( int ) ) );
// Switch between views
connect( logMainView, SIGNAL( exitView() ),
filteredView, SLOT( setFocus() ) );
connect( filteredView, SIGNAL( exitView() ),
logMainView, SLOT( setFocus() ) );
}
// Create a new search using the text passed, replace the currently
// used one and destroy the old one.
void CrawlerWidget::replaceCurrentSearch( const QString& searchText )
{
// Interrupt the search if it's ongoing
logFilteredData_->interruptSearch();
// We have to wait for the last search update (100%)
// before clearing/restarting to avoid having remaining results.
// FIXME: this is a bit of a hack, we call processEvents
// for Qt to empty its event queue, including (hopefully)
// the search update event sent by logFilteredData_. It saves
// us the overhead of having proper sync.
QApplication::processEvents( QEventLoop::ExcludeUserInputEvents );
nbMatches_ = 0;
// Clear and recompute the content of the filtered window.
logFilteredData_->clearSearch();
filteredView->updateData();
// Update the match overview
overview_.updateData( logData_->getNbLine() );
if ( !searchText.isEmpty() ) {
QString pattern;
// Determine the type of regexp depending on the config
static std::shared_ptr config =
Persistent( "settings" );
switch ( config->mainRegexpType() ) {
case FixedString:
pattern = QRegularExpression::escape(searchText);
break;
default:
pattern = searchText;
break;
}
// Set the pattern case insensitive if needed
QRegularExpression::PatternOptions patternOptions =
QRegularExpression::UseUnicodePropertiesOption
| QRegularExpression::OptimizeOnFirstUsageOption;
if ( ignoreCaseCheck->checkState() == Qt::Checked )
patternOptions |= QRegularExpression::CaseInsensitiveOption;
// Constructs the regexp
QRegularExpression regexp( pattern, patternOptions );
if ( regexp.isValid() ) {
// Activate the stop button
stopButton->setEnabled( true );
// Start a new asynchronous search
logFilteredData_->runSearch( regexp );
// Accept auto-refresh of the search
searchState_.startSearch();
}
else {
// The regexp is wrong
logFilteredData_->clearSearch();
filteredView->updateData();
searchState_.resetState();
// Inform the user
QString errorMessage = tr("Error in expression");
const int offset = regexp.patternErrorOffset();
if (offset != -1) {
errorMessage += " at position ";
errorMessage += QString::number(offset);
}
errorMessage += ": ";
errorMessage += regexp.errorString();
searchInfoLine->setPalette( errorPalette );
searchInfoLine->setText( errorMessage );
}
}
else {
searchState_.resetState();
printSearchInfoMessage();
}
}
// Updates the content of the drop down list for the saved searches,
// called when the SavedSearch has been changed.
void CrawlerWidget::updateSearchCombo()
{
const QString text = searchLineEdit->lineEdit()->text();
searchLineEdit->clear();
searchLineEdit->addItems( savedSearches_->recentSearches() );
// In case we had something that wasn't added to the list (blank...):
searchLineEdit->lineEdit()->setText( text );
}
// Print the search info message.
void CrawlerWidget::printSearchInfoMessage( int nbMatches )
{
QString text;
switch ( searchState_.getState() ) {
case SearchState::NoSearch:
// Blank text is fine
break;
case SearchState::Static:
text = tr("%1 match%2 found.").arg( nbMatches )
.arg( nbMatches > 1 ? "es" : "" );
break;
case SearchState::Autorefreshing:
text = tr("%1 match%2 found. Search is auto-refreshing...").arg( nbMatches )
.arg( nbMatches > 1 ? "es" : "" );
break;
case SearchState::FileTruncated:
case SearchState::TruncatedAutorefreshing:
text = tr("File truncated on disk, previous search results are not valid anymore.");
break;
}
searchInfoLine->setPalette( searchInfoLineDefaultPalette );
searchInfoLine->setText( text );
}
// Change the data status and, if needed, advise upstream.
void CrawlerWidget::changeDataStatus( DataStatus status )
{
if ( ( status != dataStatus_ )
&& (! ( dataStatus_ == DataStatus::NEW_FILTERED_DATA
&& status == DataStatus::NEW_DATA ) ) ) {
dataStatus_ = status;
emit dataStatusChanged( dataStatus_ );
}
}
// Determine the right encoding and set the views.
void CrawlerWidget::updateEncoding()
{
Encoding encoding = Encoding::ENCODING_MAX;
switch ( encodingSetting_ ) {
case Encoding::ENCODING_AUTO:
switch ( logData_->getDetectedEncoding() ) {
case EncodingSpeculator::Encoding::ASCII7:
encoding = Encoding::ENCODING_ISO_8859_1;
encoding_text_ = tr( "US-ASCII" );
break;
case EncodingSpeculator::Encoding::ASCII8:
encoding = Encoding::ENCODING_ISO_8859_1;
encoding_text_ = tr( "ISO-8859-1" );
break;
case EncodingSpeculator::Encoding::UTF8:
encoding = Encoding::ENCODING_UTF8;
encoding_text_ = tr( "UTF-8" );
break;
case EncodingSpeculator::Encoding::UTF16LE:
encoding = Encoding::ENCODING_UTF16LE;
encoding_text_ = tr( "UTF-16LE" );
break;
case EncodingSpeculator::Encoding::UTF16BE:
encoding = Encoding::ENCODING_UTF16BE;
encoding_text_ = tr( "UTF-16BE" );
break;
}
break;
case Encoding::ENCODING_UTF8:
encoding = encodingSetting_;
encoding_text_ = tr( "Displayed as UTF-8" );
break;
case Encoding::ENCODING_UTF16LE:
encoding = encodingSetting_;
encoding_text_ = tr( "Displayed as UTF-16LE" );
break;
case Encoding::ENCODING_UTF16BE:
encoding = encodingSetting_;
encoding_text_ = tr( "Displayed as UTF-16BE" );
break;
case Encoding::ENCODING_CP1251:
encoding = encodingSetting_;
encoding_text_ = tr( "Displayed as CP1251" );
break;
case Encoding::ENCODING_CP1252:
encoding = encodingSetting_;
encoding_text_ = tr( "Displayed as CP1252" );
break;
case Encoding::ENCODING_ISO_8859_1:
default:
encoding = Encoding::ENCODING_ISO_8859_1;
encoding_text_ = tr( "Displayed as ISO-8859-1" );
break;
}
logData_->setDisplayEncoding( encoding );
logMainView->forceRefresh();
logFilteredData_->setDisplayEncoding( encoding );
filteredView->forceRefresh();
}
// Change the respective size of the two views
void CrawlerWidget::changeTopViewSize( int32_t delta )
{
int min, max;
getRange( 1, &min, &max );
LOG(logDEBUG) << "CrawlerWidget::changeTopViewSize " << sizes()[0] << " " << min << " " << max;
moveSplitter( closestLegalPosition( sizes()[0] + ( delta * 10 ), 1 ), 1 );
LOG(logDEBUG) << "CrawlerWidget::changeTopViewSize " << sizes()[0];
}
//
// SearchState implementation
//
void CrawlerWidget::SearchState::resetState()
{
state_ = NoSearch;
}
void CrawlerWidget::SearchState::setAutorefresh( bool refresh )
{
autoRefreshRequested_ = refresh;
if ( refresh ) {
if ( state_ == Static )
state_ = Autorefreshing;
/*
else if ( state_ == FileTruncated )
state_ = TruncatedAutorefreshing;
*/
}
else {
if ( state_ == Autorefreshing )
state_ = Static;
else if ( state_ == TruncatedAutorefreshing )
state_ = FileTruncated;
}
}
void CrawlerWidget::SearchState::truncateFile()
{
if ( state_ == Autorefreshing || state_ == TruncatedAutorefreshing ) {
state_ = TruncatedAutorefreshing;
}
else {
state_ = FileTruncated;
}
}
void CrawlerWidget::SearchState::changeExpression()
{
if ( state_ == Autorefreshing )
state_ = Static;
}
void CrawlerWidget::SearchState::stopSearch()
{
if ( state_ == Autorefreshing )
state_ = Static;
}
void CrawlerWidget::SearchState::startSearch()
{
if ( autoRefreshRequested_ )
state_ = Autorefreshing;
else
state_ = Static;
}
/*
* CrawlerWidgetContext
*/
CrawlerWidgetContext::CrawlerWidgetContext( const char* string )
{
QRegularExpression regex( "S(\\d+):(\\d+)" );
QRegularExpressionMatch match = regex.match( string );
if ( match.hasMatch() ) {
sizes_ = { match.captured(1).toInt(), match.captured(2).toInt() };
LOG(logDEBUG) << "sizes_: " << sizes_[0] << " " << sizes_[1];
}
else {
LOG(logWARNING) << "Unrecognised view size: " << string;
// Default values;
sizes_ = { 100, 400 };
}
QRegularExpression case_refresh_regex( "IC(\\d+):AR(\\d+)" );
match = case_refresh_regex.match( string );
if ( match.hasMatch() ) {
ignore_case_ = ( match.captured(1).toInt() == 1 );
auto_refresh_ = ( match.captured(2).toInt() == 1 );
LOG(logDEBUG) << "ignore_case_: " << ignore_case_ << " auto_refresh_: "
<< auto_refresh_;
}
else {
LOG(logWARNING) << "Unrecognised case/refresh: " << string;
ignore_case_ = false;
auto_refresh_ = false;
}
}
std::string CrawlerWidgetContext::toString() const
{
char string[160];
snprintf( string, sizeof string, "S%d:%d:IC%d:AR%d",
sizes_[0], sizes_[1],
ignore_case_, auto_refresh_ );
return { string };
}