diff --git a/.github/actions/agent-build/action.yml b/.github/actions/agent-build/action.yml index e1d823a17..ecb8d86d0 100644 --- a/.github/actions/agent-build/action.yml +++ b/.github/actions/agent-build/action.yml @@ -15,7 +15,7 @@ runs: run: | mkdir $KLOGG_BUILD_ROOT cd $KLOGG_BUILD_ROOT - cmake $KLOGG_CMAKE_OPTS -DCPM_SOURCE_CACHE=$KLOGG_WORKSPACE/cpm_cache -DKLOGG_OVERRIDE_MALLOC=OFF $KLOGG_WORKSPACE + cmake $KLOGG_CMAKE_OPTS -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -DCPM_SOURCE_CACHE=$KLOGG_WORKSPACE/cpm_cache -DKLOGG_OVERRIDE_MALLOC=OFF $KLOGG_WORKSPACE - name: build shell: sh diff --git a/.github/actions/agent-setup/action.yml b/.github/actions/agent-setup/action.yml index 7669e2de1..67b7242f7 100644 --- a/.github/actions/agent-setup/action.yml +++ b/.github/actions/agent-setup/action.yml @@ -8,7 +8,7 @@ runs: - name: Cache Qt id: cache-qt - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ../Qt key: ${{ matrix.config.os }}-${{ matrix.config.arch }}-${{ matrix.config.qt_version }}-QtCache @@ -30,7 +30,7 @@ runs: echo "BOOST_URL=https://sourceforge.net/projects/boost/files/boost/1.86.0/boost_1_86_0.tar.bz2/download" >> $GITHUB_ENV - name: Restore Boost cache - uses: actions/cache@v2 + uses: actions/cache@v4 id: cache-boost with: path: ${{env.BOOST_ROOT}} diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 27090b427..4ea7b321f 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -34,7 +34,7 @@ jobs: - name: Save version run: echo $KLOGG_VERSION > klogg_version.txt - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: klogg_version path: 'klogg_version.txt' @@ -136,7 +136,7 @@ jobs: - uses: ./.github/actions/docker-package # Final upload of all packages - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: packages-${{ matrix.config.artifacts_id }} path: '${{ env.KLOGG_BUILD_ROOT }}/packages/*' @@ -197,7 +197,7 @@ jobs: notarization-team: ${{ secrets.NOTARIZATION_TEAM }} notarization-password: ${{ secrets.NOTARIZATION_PASSWORD }} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: packages-${{ matrix.config.artifacts_id }} path: '${{ env.KLOGG_BUILD_ROOT }}/packages/*' @@ -233,7 +233,7 @@ jobs: - name: Cache openssl id: cache-openssl - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ github.workspace }}\openssl-1.1 key: OpensslCache-1-1-1w @@ -268,7 +268,7 @@ jobs: s3-bucket: ${{ secrets.WIN_CS_BUCKET }} # Final upload of all packages - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: packages-${{ matrix.config.artifacts_id }} path: '${{ env.KLOGG_BUILD_ROOT }}/packages/*' diff --git a/packaging/windows/prepare_release.cmd b/packaging/windows/prepare_release.cmd index 9b5338bae..8df9020d6 100644 --- a/packaging/windows/prepare_release.cmd +++ b/packaging/windows/prepare_release.cmd @@ -12,15 +12,8 @@ xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\output\klogg.pdb %KLOGG_WORKSPACE%\re xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\output\klogg_crashpad_handler.exe %KLOGG_WORKSPACE%\release\ /y xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\output\klogg_minidump_dump.exe %KLOGG_WORKSPACE%\release\ /y -xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\msvc_19.41_cxx17_64_md_relwithdebinfo\tbb12.dll %KLOGG_WORKSPACE%\release\ /y -xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\msvc_19.41_cxx17_64_md_relwithdebinfo\tbb12.pdb %KLOGG_WORKSPACE%\release\ /y -xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\msvc_19.41_cxx17_32_md_relwithdebinfo\tbb12.dll %KLOGG_WORKSPACE%\release\ /y -xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\msvc_19.41_cxx17_32_md_relwithdebinfo\tbb12.pdb %KLOGG_WORKSPACE%\release\ /y - -xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\msvc_19.42_cxx17_64_md_relwithdebinfo\tbb12.dll %KLOGG_WORKSPACE%\release\ /y -xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\msvc_19.42_cxx17_64_md_relwithdebinfo\tbb12.pdb %KLOGG_WORKSPACE%\release\ /y -xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\msvc_19.42_cxx17_32_md_relwithdebinfo\tbb12.dll %KLOGG_WORKSPACE%\release\ /y -xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\msvc_19.42_cxx17_32_md_relwithdebinfo\tbb12.pdb %KLOGG_WORKSPACE%\release\ /y +for /r "%KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%" %%F in (tbb12.dll) do xcopy "%%F" %KLOGG_WORKSPACE%\release\ /y +for /r "%KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%" %%F in (tbb12.pdb) do xcopy "%%F" %KLOGG_WORKSPACE%\release\ /y xcopy %KLOGG_WORKSPACE%\%KLOGG_BUILD_ROOT%\generated\documentation.html %KLOGG_WORKSPACE%\release\ /y xcopy %KLOGG_WORKSPACE%\COPYING %KLOGG_WORKSPACE%\release\ /y diff --git a/src/ui/include/crawlerwidget.h b/src/ui/include/crawlerwidget.h index 7fe1a4dd6..57ffc2f7d 100644 --- a/src/ui/include/crawlerwidget.h +++ b/src/ui/include/crawlerwidget.h @@ -335,7 +335,7 @@ class CrawlerWidget : public QSplitter, void updatePredefinedFiltersWidget(); // Reload predefined filters after changing settings - void reloadPredefinedFilters() const; + void reloadPredefinedFilters(); QString escapeSearchPattern( const QString& searchPattern, bool isRegex = false ) const; QString& combinePatterns( QString& currentPattern, const QString& newPattern ) const; @@ -359,6 +359,7 @@ class CrawlerWidget : public QSplitter, SavedSearches* savedSearches_ = nullptr; std::shared_ptr logData_; + std::unique_ptr logMainViewData_; std::shared_ptr logFilteredData_; // Matches overview @@ -394,7 +395,6 @@ class CrawlerWidget : public QSplitter, QToolButton* inverseButton_; QToolButton* booleanButton_; QToolButton* searchRefreshButton_; - std::map shortcuts_; // Default palette to be remembered diff --git a/src/ui/include/logmainview.h b/src/ui/include/logmainview.h index 2c75f2bc3..694222dab 100644 --- a/src/ui/include/logmainview.h +++ b/src/ui/include/logmainview.h @@ -40,20 +40,72 @@ #ifndef LOGMAINVIEW_H #define LOGMAINVIEW_H +#include + #include "abstractlogview.h" #include "logdata.h" +#include "logfiltereddata.h" + +class LogMainViewData : public AbstractLogData +{ + Q_OBJECT + + public: + explicit LogMainViewData( const LogData* sourceLogData ); + + void setTopFilter( const RegularExpressionPattern& pattern ); + void prepareFullRefresh(); + void refreshSearch(); + void interruptSearch(); + + LineNumber getSourceLineNumber( LineNumber index ) const; + LineNumber getLineIndexNumber( LineNumber sourceLine ) const; + LinesCount getNbTotalLines() const; + + Q_SIGNALS: + void searchProgressed( LinesCount nbMatches, int progress, LineNumber initialLine ); + + protected: + QString doGetLineString( LineNumber line ) const override; + QString doGetExpandedLineString( LineNumber line ) const override; + klogg::vector doGetLines( LineNumber firstLine, LinesCount number ) const override; + klogg::vector doGetExpandedLines( LineNumber firstLine, + LinesCount number ) const override; + LineNumber doGetLineNumber( LineNumber index ) const override; + LinesCount doGetNbLine() const override; + LineLength doGetMaxLength() const override; + LineLength doGetLineLength( LineNumber line ) const override; + void doSetDisplayEncoding( const char* encoding ) override; + QTextCodec* doGetDisplayEncoding() const override; + void doAttachReader() const override; + void doDetachReader() const override; + + private: + const AbstractLogData* activeData() const; + + const LogData* sourceLogData_; + std::unique_ptr filteredData_; + RegularExpressionPattern filterPattern_; + bool filterEnabled_ = false; + bool fullRefreshRequired_ = false; +}; // Class implementing the main (top) view widget. class LogMainView : public AbstractLogView { Q_OBJECT public: - LogMainView( const LogData* newLogData, + LogMainView( LogMainViewData* newLogData, const QuickFindPattern* const quickFindPattern, Overview* overview, OverviewWidget* overview_widget, QWidget* parent = nullptr ); + LineNumber getTopSourceLine() const; + void trySelectSourceLine( LineNumber sourceLine ); + void selectSourcePortionAndDisplayLine( LineNumber sourceLine, LinesCount nLines, + LineColumn startCol, LineLength nSymbols ); + // Configure the view to use the passed filtered list // (used for couloured bullets) // Should be NULL or the empty LFD if no filtering is used @@ -63,10 +115,15 @@ class LogMainView : public AbstractLogView // Implements the virtual function LogData::LineType lineType( LineNumber lineNumber ) const override; + LineNumber displayLineNumber( LineNumber lineNumber ) const override; + LineNumber lineIndex( LineNumber lineNumber ) const override; + LineNumber maxDisplayLineNumber() const override; + void doRegisterShortcuts() override; private: LogFilteredData* filteredData_; + LogMainViewData* logMainViewData_; }; #endif diff --git a/src/ui/include/predefinedfilters.h b/src/ui/include/predefinedfilters.h index 09983b1be..dd9c6208e 100644 --- a/src/ui/include/predefinedfilters.h +++ b/src/ui/include/predefinedfilters.h @@ -49,6 +49,7 @@ struct PredefinedFilter { QString name; QString pattern; bool useRegex; + bool filterTop = false; }; // Represents collection of filters read from settings file. @@ -70,11 +71,11 @@ class PredefinedFiltersCollection final : public PersistableupdateCurrentPosition( firstLine_, lastLine ); + if ( lastLineNumber > 0_lnum ) { + const auto firstSourceLine = displayLineNumber( firstLine_ ) - 1_lcount; + const auto lastSourceLine = lastLine > firstLine_ + ? displayLineNumber( lastLine - 1_lcount ) + : firstSourceLine; + overview_->updateCurrentPosition( firstSourceLine, lastSourceLine ); + } + else { + overview_->updateCurrentPosition( 0_lnum, 0_lnum ); + } } forceRefresh(); diff --git a/src/ui/src/crawlerwidget.cpp b/src/ui/src/crawlerwidget.cpp index c8d86f0f3..b17a059f8 100644 --- a/src/ui/src/crawlerwidget.cpp +++ b/src/ui/src/crawlerwidget.cpp @@ -136,7 +136,6 @@ class CrawlerWidgetContext : public ViewContextInterface { { return useBooleanCombination_; } - QList marks() const { return marks_; @@ -155,7 +154,6 @@ class CrawlerWidgetContext : public ViewContextInterface { bool useRegexp_; bool inverseRegexp_; bool useBooleanCombination_; - QList marks_; }; @@ -170,7 +168,7 @@ CrawlerWidget::CrawlerWidget( QWidget* parent ) // The top line is first one on the main display LineNumber CrawlerWidget::getTopLine() const { - return logMainView_->getTopLine(); + return logMainView_->getTopSourceLine(); } QString CrawlerWidget::getSelectedText() const @@ -209,9 +207,26 @@ bool CrawlerWidget::isTextWrapEnabled() const return logMainView_->isTextWrapEnabled(); } -void CrawlerWidget::reloadPredefinedFilters() const +void CrawlerWidget::reloadPredefinedFilters() { predefinedFilters_->populatePredefinedFilters(); + + QStringList topPatterns; + const auto filters = PredefinedFiltersCollection::getSynced().getFilters(); + for ( const auto& filter : filters ) { + if ( !filter.filterTop ) { + continue; + } + + const auto pattern + = filter.useRegex ? filter.pattern : QRegularExpression::escape( filter.pattern ); + topPatterns.append( QString( "(?:%1)" ).arg( pattern ) ); + } + + const RegularExpressionPattern topFilterPattern{ topPatterns.join( '|' ), false, true, false, + false }; + logMainViewData_->setTopFilter( topFilterPattern ); + logMainView_->updateData(); } QString CrawlerWidget::encodingText() const @@ -259,6 +274,7 @@ void CrawlerWidget::changeEvent( QEvent* event ) void CrawlerWidget::stopLoading() { + logMainViewData_->interruptSearch(); logFilteredData_->interruptSearch(); logData_->interruptLoading(); } @@ -266,6 +282,7 @@ void CrawlerWidget::stopLoading() void CrawlerWidget::reload() { searchState_.resetState(); + logMainViewData_->prepareFullRefresh(); constexpr auto DropCache = true; logFilteredData_->clearSearch( DropCache ); logFilteredData_->clearMarks(); @@ -306,7 +323,7 @@ void CrawlerWidget::goToLine() const auto selectedLine = LineNumber( static_cast( newLine - 1 ) ); filteredView_->trySelectLine( logFilteredData_->getLineIndexNumber( selectedLine ) ); - logMainView_->trySelectLine( selectedLine ); + logMainView_->trySelectSourceLine( selectedLine ); } } @@ -548,8 +565,8 @@ void CrawlerWidget::jumpToMatchingLine( LineNumber filteredLineNb, LinesCount nL LineColumn startCol, LineLength nSymbols ) { const auto mainViewLine = logFilteredData_->getMatchingLineNumber( filteredLineNb ); - logMainView_->selectPortionAndDisplayLine( mainViewLine, nLines, startCol, - nSymbols ); // FIXME: should be done with a signal. + logMainView_->selectSourcePortionAndDisplayLine( + mainViewLine, nLines, startCol, nSymbols ); // FIXME: should be done with a signal. } void CrawlerWidget::updateLineNumberHandler( LineNumber line, LinesCount nLines, @@ -695,6 +712,8 @@ void CrawlerWidget::loadingFinishedHandler( LoadingStatus status ) // overview have probably changed. overview_.updateData( logData_->getNbLine() ); + logMainViewData_->refreshSearch(); + // FIXME, handle topLine // logMainView_->updateData( logData_, topLine ); logMainView_->updateData(); @@ -739,6 +758,8 @@ void CrawlerWidget::fileChangedHandler( MonitoredFileStatus status ) { // Handle the case where the file has been truncated if ( status == MonitoredFileStatus::Truncated ) { + logMainViewData_->prepareFullRefresh(); + // Clear all marks (TODO offer the option to keep them) logFilteredData_->clearMarks(); if ( !searchInfoLine_->text().isEmpty() ) { @@ -968,8 +989,10 @@ void CrawlerWidget::setup() bottomWindow->setContentsMargins( 2, 0, 2, 0 ); overviewWidget_ = new OverviewWidget(); + logMainViewData_ = std::make_unique( logData_.get() ); logMainView_ - = new LogMainView( logData_.get(), quickFindPattern_.get(), &overview_, overviewWidget_ ); + = new LogMainView( logMainViewData_.get(), quickFindPattern_.get(), &overview_, + overviewWidget_ ); logMainView_->setContentsMargins( 2, 0, 2, 0 ); filteredView_ = new FilteredView( logFilteredData_.get(), quickFindPattern_.get() ); @@ -1207,9 +1230,21 @@ void CrawlerWidget::setup() [ this ]( auto ) { logMainView_->update(); } ); connect( logMainView_, &LogMainView::newSelection, this, - &CrawlerWidget::updateLineNumberHandler ); - - connect( logMainView_, &LogMainView::markLines, this, &CrawlerWidget::markLinesFromMain ); + [ this ]( LineNumber line, LinesCount nLines, LineColumn startCol, + LineLength nSymbols ) { + updateLineNumberHandler( logMainViewData_->getSourceLineNumber( line ), nLines, + startCol, nSymbols ); + } ); + + connect( logMainView_, &LogMainView::markLines, this, + [ this ]( const klogg::vector& lines ) { + klogg::vector sourceLines( lines.size() ); + std::transform( lines.cbegin(), lines.cend(), sourceLines.begin(), + [ this ]( const auto& line ) { + return logMainViewData_->getSourceLineNumber( line ); + } ); + markLinesFromMain( sourceLines ); + } ); connect( logMainView_, &LogMainView::highlightersChange, this, &CrawlerWidget::applyConfiguration ); @@ -1253,6 +1288,10 @@ void CrawlerWidget::setup() connect( logFilteredData_.get(), &LogFilteredData::searchProgressed, this, &CrawlerWidget::updateFilteredView, Qt::QueuedConnection ); + connect( logMainViewData_.get(), &LogMainViewData::searchProgressed, this, + [ this ]( LinesCount, int, LineNumber ) { logMainView_->updateData(); }, + Qt::QueuedConnection ); + // Sent load file update to MainWindow (for status update) connect( logData_.get(), &LogData::loadingProgressed, this, &CrawlerWidget::loadingProgressed ); connect( logData_.get(), &LogData::loadingFinished, this, diff --git a/src/ui/src/logmainview.cpp b/src/ui/src/logmainview.cpp index e6506b982..43f14d6a6 100644 --- a/src/ui/src/logmainview.cpp +++ b/src/ui/src/logmainview.cpp @@ -50,9 +50,152 @@ #include "shortcuts.h" -LogMainView::LogMainView( const LogData* newLogData, const QuickFindPattern* const quickFindPattern, +LogMainViewData::LogMainViewData( const LogData* sourceLogData ) + : sourceLogData_( sourceLogData ) + , filteredData_( sourceLogData->getNewFilteredData() ) +{ + connect( filteredData_.get(), &LogFilteredData::searchProgressed, this, + &LogMainViewData::searchProgressed ); +} + +void LogMainViewData::setTopFilter( const RegularExpressionPattern& pattern ) +{ + const auto enableFilter = !pattern.pattern.isEmpty(); + if ( filterEnabled_ == enableFilter && filterPattern_.pattern == pattern.pattern + && filterPattern_.isCaseSensitive == pattern.isCaseSensitive ) { + return; + } + + filteredData_->interruptSearch(); + filterPattern_ = pattern; + filterEnabled_ = enableFilter; + fullRefreshRequired_ = false; + + if ( filterEnabled_ ) { + filteredData_->runSearch( filterPattern_ ); + } + else { + filteredData_->clearSearch(); + } +} + +void LogMainViewData::prepareFullRefresh() +{ + if ( filterEnabled_ ) { + constexpr auto DropCache = true; + filteredData_->clearSearch( DropCache ); + fullRefreshRequired_ = true; + } +} + +void LogMainViewData::refreshSearch() +{ + if ( !filterEnabled_ ) { + return; + } + + if ( fullRefreshRequired_ ) { + fullRefreshRequired_ = false; + filteredData_->runSearch( filterPattern_ ); + } + else { + filteredData_->updateSearch( 0_lnum, LineNumber( sourceLogData_->getNbLine().get() ) ); + } +} + +void LogMainViewData::interruptSearch() +{ + filteredData_->interruptSearch(); +} + +LineNumber LogMainViewData::getSourceLineNumber( LineNumber index ) const +{ + return filterEnabled_ ? filteredData_->getMatchingLineNumber( index ) : index; +} + +LineNumber LogMainViewData::getLineIndexNumber( LineNumber sourceLine ) const +{ + return filterEnabled_ ? filteredData_->getLineIndexNumber( sourceLine ) : sourceLine; +} + +LinesCount LogMainViewData::getNbTotalLines() const +{ + return sourceLogData_->getNbLine(); +} + +const AbstractLogData* LogMainViewData::activeData() const +{ + return filterEnabled_ ? static_cast( filteredData_.get() ) + : static_cast( sourceLogData_ ); +} + +QString LogMainViewData::doGetLineString( LineNumber line ) const +{ + return activeData()->getLineString( line ); +} + +QString LogMainViewData::doGetExpandedLineString( LineNumber line ) const +{ + return activeData()->getExpandedLineString( line ); +} + +klogg::vector LogMainViewData::doGetLines( LineNumber firstLine, + LinesCount number ) const +{ + return activeData()->getLines( firstLine, number ); +} + +klogg::vector LogMainViewData::doGetExpandedLines( LineNumber firstLine, + LinesCount number ) const +{ + return activeData()->getExpandedLines( firstLine, number ); +} + +LineNumber LogMainViewData::doGetLineNumber( LineNumber index ) const +{ + return getSourceLineNumber( index ); +} + +LinesCount LogMainViewData::doGetNbLine() const +{ + return activeData()->getNbLine(); +} + +LineLength LogMainViewData::doGetMaxLength() const +{ + return activeData()->getMaxLength(); +} + +LineLength LogMainViewData::doGetLineLength( LineNumber line ) const +{ + return activeData()->getLineLength( line ); +} + +void LogMainViewData::doSetDisplayEncoding( const char* encoding ) +{ + Q_UNUSED( encoding ) +} + +QTextCodec* LogMainViewData::doGetDisplayEncoding() const +{ + return sourceLogData_->getDisplayEncoding(); +} + +void LogMainViewData::doAttachReader() const +{ + sourceLogData_->attachReader(); +} + +void LogMainViewData::doDetachReader() const +{ + sourceLogData_->detachReader(); +} + +LogMainView::LogMainView( LogMainViewData* newLogData, + const QuickFindPattern* const quickFindPattern, Overview* overview, OverviewWidget* overview_widget, QWidget* parent ) : AbstractLogView( newLogData, quickFindPattern, parent ) + , logMainViewData_( newLogData ) { filteredData_ = nullptr; @@ -60,6 +203,27 @@ LogMainView::LogMainView( const LogData* newLogData, const QuickFindPattern* con setOverview( overview, overview_widget ); } +LineNumber LogMainView::getTopSourceLine() const +{ + return logMainViewData_->getSourceLineNumber( getTopLine() ); +} + +void LogMainView::trySelectSourceLine( LineNumber sourceLine ) +{ + if ( logMainViewData_->getNbLine() > 0_lcount ) { + trySelectLine( logMainViewData_->getLineIndexNumber( sourceLine ) ); + } +} + +void LogMainView::selectSourcePortionAndDisplayLine( LineNumber sourceLine, LinesCount nLines, + LineColumn startCol, LineLength nSymbols ) +{ + if ( logMainViewData_->getNbLine() > 0_lcount ) { + selectPortionAndDisplayLine( logMainViewData_->getLineIndexNumber( sourceLine ), nLines, + startCol, nSymbols ); + } +} + // Just update our internal record. void LogMainView::useNewFiltering( LogFilteredData* filteredData ) { @@ -74,25 +238,43 @@ void LogMainView::useNewFiltering( LogFilteredData* filteredData ) AbstractLogData::LineType LogMainView::lineType( LineNumber lineNumber ) const { if ( filteredData_ ) { - return filteredData_->lineTypeByLine( lineNumber ); + return filteredData_->lineTypeByLine( + logMainViewData_->getSourceLineNumber( lineNumber ) ); } return AbstractLogData::LineTypeFlags::Plain; } +LineNumber LogMainView::displayLineNumber( LineNumber lineNumber ) const +{ + return logMainViewData_->getSourceLineNumber( lineNumber ) + 1_lcount; +} + +LineNumber LogMainView::lineIndex( LineNumber lineNumber ) const +{ + return logMainViewData_->getLineIndexNumber( lineNumber ); +} + +LineNumber LogMainView::maxDisplayLineNumber() const +{ + return LineNumber( logMainViewData_->getNbTotalLines().get() ); +} + void LogMainView::doRegisterShortcuts() { LOG_INFO << "Registering shortcuts for main view"; AbstractLogView::doRegisterShortcuts(); registerShortcut( ShortcutAction::LogViewNextMark, [ this ] { - const auto line = filteredData_->getMarkAfter( getViewPosition() ); + const auto line = filteredData_->getMarkAfter( + logMainViewData_->getSourceLineNumber( getViewPosition() ) ); if ( line.has_value() ) { - selectAndDisplayLine( *line ); + trySelectSourceLine( *line ); } } ); registerShortcut( ShortcutAction::LogViewPrevMark, [ this ] { - const auto line = filteredData_->getMarkBefore( getViewPosition() ); + const auto line = filteredData_->getMarkBefore( + logMainViewData_->getSourceLineNumber( getViewPosition() ) ); if ( line.has_value() ) { - selectAndDisplayLine( *line ); + trySelectSourceLine( *line ); } } ); } diff --git a/src/ui/src/predefinedfilters.cpp b/src/ui/src/predefinedfilters.cpp index f83a84c29..b6866eed1 100644 --- a/src/ui/src/predefinedfilters.cpp +++ b/src/ui/src/predefinedfilters.cpp @@ -57,7 +57,8 @@ void PredefinedFiltersCollection::retrieveFromStorage( QSettings& settings ) filters_.push_back( { settings.value( "name" ).toString(), settings.value( "filter" ).toString(), - settings.value( "regex", true ).toBool() } ); + settings.value( "regex", true ).toBool(), + settings.value( "filterTop", false ).toBool() } ); } settings.endArray(); } @@ -84,6 +85,7 @@ void PredefinedFiltersCollection::saveToStorage( QSettings& settings ) const settings.setValue( "name", filter.name ); settings.setValue( "filter", filter.pattern ); settings.setValue( "regex", filter.useRegex ); + settings.setValue( "filterTop", filter.filterTop ); arrayIndex++; } diff --git a/src/ui/src/predefinedfiltersdialog.cpp b/src/ui/src/predefinedfiltersdialog.cpp index 7884f69ea..9f3da1d98 100644 --- a/src/ui/src/predefinedfiltersdialog.cpp +++ b/src/ui/src/predefinedfiltersdialog.cpp @@ -153,10 +153,11 @@ void PredefinedFiltersDialog::populateFiltersTable( filtersTableWidget->clear(); filtersTableWidget->setRowCount( static_cast( filters.size() ) ); - filtersTableWidget->setColumnCount( 3 ); + filtersTableWidget->setColumnCount( 4 ); filtersTableWidget->setHorizontalHeaderLabels( QStringList() << tr( "Name" ) << tr( "Pattern" ) - << tr( "Regex" ) ); + << tr( "Regex" ) + << tr( "Filter Top" ) ); int filterIndex = 0; for ( const auto& filter : filters ) { @@ -165,12 +166,17 @@ void PredefinedFiltersDialog::populateFiltersTable( auto* regexCheckbox = new CenteredCheckbox; regexCheckbox->setChecked( filter.useRegex ); filtersTableWidget->setCellWidget( filterIndex, 2, regexCheckbox ); + auto* filterTopCheckbox = new CenteredCheckbox; + filterTopCheckbox->setChecked( filter.filterTop ); + filtersTableWidget->setCellWidget( filterIndex, 3, filterTopCheckbox ); filterIndex++; } filtersTableWidget->horizontalHeader()->setSectionResizeMode( 0, QHeaderView::ResizeToContents ); filtersTableWidget->horizontalHeader()->setSectionResizeMode( 1, QHeaderView::Stretch ); + filtersTableWidget->horizontalHeader()->setSectionResizeMode( 2, QHeaderView::ResizeToContents ); + filtersTableWidget->horizontalHeader()->setSectionResizeMode( 3, QHeaderView::ResizeToContents ); filtersTableWidget->verticalHeader()->setSectionResizeMode( QHeaderView::ResizeToContents ); filtersTableWidget->setWordWrap( false ); @@ -200,9 +206,12 @@ PredefinedFiltersCollection::Collection PredefinedFiltersDialog::readFiltersTabl const auto useRegexCheckbox = static_cast( filtersTableWidget->cellWidget( i, 2 ) ); const auto useRegex = useRegexCheckbox ? useRegexCheckbox->isChecked() : false; + const auto filterTopCheckbox + = static_cast( filtersTableWidget->cellWidget( i, 3 ) ); + const auto filterTop = filterTopCheckbox ? filterTopCheckbox->isChecked() : false; if ( !name.isEmpty() && !value.isEmpty() ) { - currentFilters.push_back( { name, value, useRegex } ); + currentFilters.push_back( { name, value, useRegex, filterTop } ); } } @@ -222,6 +231,8 @@ void PredefinedFiltersDialog::addFilterRow( const QString& newFilter ) filtersTableWidget->setItem( newRow, 0, new QTableWidgetItem( "" ) ); auto regexCheckBox = new CenteredCheckbox; filtersTableWidget->setCellWidget( newRow, 2, regexCheckBox ); + auto filterTopCheckBox = new CenteredCheckbox; + filtersTableWidget->setCellWidget( newRow, 3, filterTopCheckBox ); filtersTableWidget->scrollToItem( filtersTableWidget->item( newRow, 0 ) ); filtersTableWidget->setCurrentCell( newRow, 0 ); diff --git a/tests/ui/CMakeLists.txt b/tests/ui/CMakeLists.txt index c126c12e0..adbe9846f 100644 --- a/tests/ui/CMakeLists.txt +++ b/tests/ui/CMakeLists.txt @@ -3,6 +3,7 @@ set(UI_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/logdata_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/logfiltereddata_test.cpp ${CMAKE_CURRENT_SOURCE_DIR}/crawlerwidget_test.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/predefinedfilters_test.cpp ) if(NOT APPLE) @@ -19,4 +20,4 @@ add_backward(klogg_itests) add_test( NAME klogg_itests COMMAND klogg_itests -platform offscreen -) \ No newline at end of file +) diff --git a/tests/ui/crawlerwidget_test.cpp b/tests/ui/crawlerwidget_test.cpp index fec033149..b484c50df 100644 --- a/tests/ui/crawlerwidget_test.cpp +++ b/tests/ui/crawlerwidget_test.cpp @@ -61,7 +61,6 @@ bool generateDataFiles( QTemporaryFile& file ) return true; } - } // namespace struct CrawlerWidgetPrivate { @@ -86,6 +85,27 @@ struct CrawlerWidget::access_by { return crawler->logFilteredData_->getNbLine(); } + LinesCount getTopViewNbLines() + { + return crawler->logMainViewData_->getNbLine(); + } + + QString getTopViewLine( LineNumber line ) + { + return crawler->logMainViewData_->getLineString( line ); + } + + LineNumber getTopViewSourceLine( LineNumber line ) + { + return crawler->logMainViewData_->getSourceLineNumber( line ); + } + + void setTopFilter( const QString& pattern ) + { + crawler->logMainViewData_->setTopFilter( + RegularExpressionPattern{ pattern, false, true, false, false } ); + } + void selectAllInMainView() { crawler->logMainView_->selectAll(); @@ -266,3 +286,31 @@ SCENARIO( "Crawler widget search", "[ui]" ) } } } + +SCENARIO( "Top filters are independent from bottom searches", "[ui]" ) +{ + QTemporaryFile file{ "crawler_top_filter_test_XXXXXX" }; + REQUIRE( generateDataFiles( file ) ); + + Session session; + CrawlerWidgetVisitor crawlerVisitor; + crawlerVisitor.crawler.reset( static_cast( + session.open( file.fileName(), []() { return new CrawlerWidget(); } ) ) ); + + waitUiState( [ & ]() { return crawlerVisitor.getLogNbLines().get() == SL_NB_LINES; } ); + waitUiState( [ & ]() { return crawlerVisitor.isLoadingFinished(); } ); + + crawlerVisitor.setTopFilter( "line 00000[0-4]" ); + + waitUiState( [ & ]() { return crawlerVisitor.getTopViewNbLines().get() == 95; } ); + REQUIRE( crawlerVisitor.getTopViewSourceLine( 0_lnum ) == 5_lnum ); + REQUIRE( crawlerVisitor.getTopViewLine( 0_lnum ).contains( "line 000005" ) ); + + crawlerVisitor.setSearchPattern( "line" ); + crawlerVisitor.runSearch(); + + waitUiState( [ & ]() { + return crawlerVisitor.getLogFilteredNbLines().get() == SL_NB_LINES; + } ); + REQUIRE( crawlerVisitor.getTopViewNbLines().get() == 95 ); +} diff --git a/tests/ui/predefinedfilters_test.cpp b/tests/ui/predefinedfilters_test.cpp new file mode 100644 index 000000000..f69233eff --- /dev/null +++ b/tests/ui/predefinedfilters_test.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2026 klogg contributors + * + * This file is part of klogg. + * + * klogg 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. + */ + +#include + +#include +#include + +#include "predefinedfilters.h" + +SCENARIO( "Top filter settings are persisted", "[ui]" ) +{ + QTemporaryFile file{ "predefined_filters_test_XXXXXX.conf" }; + REQUIRE( file.open() ); + file.close(); + + GIVEN( "a saved top filter" ) + { + QSettings settings{ file.fileName(), QSettings::IniFormat }; + PredefinedFiltersCollection savedCollection; + savedCollection.setFilters( { { "Noise", "group change option", false, true } } ); + savedCollection.saveToStorage( settings ); + settings.sync(); + + WHEN( "the filters are loaded" ) + { + PredefinedFiltersCollection loadedCollection; + loadedCollection.retrieveFromStorage( settings ); + const auto filters = loadedCollection.getFilters(); + + THEN( "the top filter flag is restored" ) + { + REQUIRE( filters.size() == 1 ); + REQUIRE( filters.front().filterTop ); + } + } + } +} + +SCENARIO( "Older predefined filters remain compatible", "[ui]" ) +{ + QTemporaryFile file{ "predefined_filters_legacy_test_XXXXXX.conf" }; + REQUIRE( file.open() ); + file.close(); + + QSettings settings{ file.fileName(), QSettings::IniFormat }; + settings.beginGroup( "PredefinedFiltersCollection" ); + settings.setValue( "version", 2 ); + settings.beginWriteArray( "filters" ); + settings.setArrayIndex( 0 ); + settings.setValue( "name", "Existing filter" ); + settings.setValue( "filter", "ShipTrail.lua" ); + settings.setValue( "regex", false ); + settings.endArray(); + settings.endGroup(); + settings.sync(); + + PredefinedFiltersCollection collection; + collection.retrieveFromStorage( settings ); + const auto filters = collection.getFilters(); + + REQUIRE( filters.size() == 1 ); + REQUIRE_FALSE( filters.front().filterTop ); +}