11 _batchTimer.setSingleShot(
true);
12 (void)connect(&_batchTimer, &QChronoTimer::timeout,
this, &LogModel::_flushPending);
14 _filterTextDebounce.setInterval(kFilterDebounceMs);
15 _filterTextDebounce.setSingleShot(
true);
16 connect(&_filterTextDebounce, &QTimer::timeout,
this, [
this]() {
17 if (_filterText != _pendingFilterText) {
18 _filterText = _pendingFilterText;
19 emit filterTextChanged();
23 _rebuildFilteredIndices();
30 if (parent.isValid()) {
33 return _filterBypassed ?
static_cast<int>(_entries.size())
34 :
static_cast<int>(_filteredIndices.size());
45 return entry->
timestamp.toString(QStringLiteral(
"hh:mm:ss.zzz"));
59 for (
auto& roleData : roleDataSpan) {
60 if (roleData.role() == Qt::DisplayRole && !useElapsed) {
61 roleData.setData(entry->
timestamp.toString(QStringLiteral(
"hh:mm:ss.zzz")));
62 }
else if (roleData.role() == Qt::DisplayRole) {
65 roleData.setData(entry->
roleData(roleData.role()));
79 if (_filterBypassed) {
80 if (row >=
static_cast<int>(_entries.size())) {
83 return &_entries[row];
86 if (row >=
static_cast<int>(_filteredIndices.size())) {
89 return &_entries[_filteredIndices[row]];
94 max = qMax(1000, max);
95 if (_maxEntries != max) {
103 if (_filterLevel != level) {
104 _filterLevel = level;
106 _rebuildFilteredIndices();
112 if (_filterCategory != category) {
113 _filterCategory = category;
115 _rebuildFilteredIndices();
121 if (_filterText != text) {
122 _pendingFilterText = text;
123 _filterTextDebounce.stop();
129 _rebuildFilteredIndices();
135 if (_pendingFilterText != text) {
136 _pendingFilterText = text;
137 _filterTextDebounce.start();
143 if (_filterRegex != enabled) {
144 _filterRegex = enabled;
146 if (enabled && !_filterText.isEmpty()) {
151 _rebuildFilteredIndices();
157 bool changed =
false;
158 _filterTextDebounce.stop();
159 _pendingFilterText.clear();
166 if (!_filterCategory.isEmpty()) {
167 _filterCategory.clear();
171 if (!_filterText.isEmpty()) {
177 _filterRegex =
false;
183 _rebuildFilteredIndices();
187bool LogModel::_hasActiveFilter()
const
189 return _filterLevel >
LogEntry::Debug || !_filterCategory.isEmpty() || !_filterText.isEmpty();
192bool LogModel::_passesFilter(
const LogEntry& entry)
const
194 if (
static_cast<int>(entry.
level) < _filterLevel) {
197 if (!_filterCategory.isEmpty() && !entry.
category.contains(_filterCategory, Qt::CaseInsensitive)) {
200 if (!_filterText.isEmpty()) {
202 if (!_compiledRegex.isValid() || !_compiledRegex.match(entry.
message).hasMatch()) {
206 if (!entry.
message.contains(_filterText, Qt::CaseInsensitive)) {
214void LogModel::_recompileRegex()
216 _compiledRegex = QRegularExpression(_filterText, QRegularExpression::CaseInsensitiveOption);
222 _pendingEntries.push_back(std::move(entry));
223 if (
static_cast<int>(_pendingEntries.size()) >= kBatchMaxSize) {
226 }
else if (!_batchTimer.isActive()) {
231void LogModel::_flushPending()
235 if (_pendingEntries.empty()) {
241 bool newCategories =
false;
242 for (
auto& entry : _pendingEntries) {
243 if (!entry.
category.isEmpty() && !_categoriesSet.contains(entry.
category)) {
244 _categoriesSet.insert(entry.
category);
245 newCategories =
true;
249 const int overflow =
static_cast<int>(_entries.size() + _pendingEntries.size()) - _maxEntries;
250 if (overflow > 0 && overflow <=
static_cast<int>(_pendingEntries.size())) {
251 _pendingEntries.erase(_pendingEntries.begin(), _pendingEntries.begin() + overflow);
254 const int firstNew =
static_cast<int>(_entries.size());
255 for (
auto& entry : _pendingEntries) {
256 _entries.push_back(std::move(entry));
258 _pendingEntries.clear();
260 const int lastNew =
static_cast<int>(_entries.size()) - 1;
261 if (firstNew <= lastNew) {
262 _appendToFiltered(firstNew, lastNew);
268 _invalidateCategoryCache();
273void LogModel::_appendToFiltered(
int first,
int last)
275 if (_filterBypassed) {
277 beginInsertRows(QModelIndex(), first, last);
282 std::vector<int> newIndices;
283 for (
int i = first; i <= last; ++i) {
284 if (_passesFilter(_entries[i])) {
285 newIndices.push_back(i);
289 if (!newIndices.empty()) {
290 const int proxyFirst =
static_cast<int>(_filteredIndices.size());
291 beginInsertRows(QModelIndex(), proxyFirst, proxyFirst +
static_cast<int>(newIndices.size()) - 1);
292 _filteredIndices.insert(_filteredIndices.end(), newIndices.begin(), newIndices.end());
297void LogModel::_trimExcess()
299 const int total =
static_cast<int>(_entries.size()) +
static_cast<int>(_pendingEntries.size());
300 if (total <= _maxEntries) {
304 const int removeCount = total - _maxEntries;
305 const int actualRemove = qMin(removeCount,
static_cast<int>(_entries.size()));
306 if (actualRemove <= 0) {
310 if (_filterBypassed) {
311 beginRemoveRows(QModelIndex(), 0, actualRemove - 1);
312 _entries.erase(_entries.begin(), _entries.begin() + actualRemove);
317 int filteredRemoved = 0;
318 while (filteredRemoved <
static_cast<int>(_filteredIndices.size()) &&
319 _filteredIndices[filteredRemoved] < actualRemove) {
323 if (filteredRemoved > 0) {
324 beginRemoveRows(QModelIndex(), 0, filteredRemoved - 1);
325 _filteredIndices.erase(_filteredIndices.begin(), _filteredIndices.begin() + filteredRemoved);
329 _entries.erase(_entries.begin(), _entries.begin() + actualRemove);
331 for (
auto& idx : _filteredIndices) {
336void LogModel::_rebuildFilteredIndices()
339 _filteredIndices.clear();
341 if (!_hasActiveFilter()) {
342 _filterBypassed =
true;
344 _filterBypassed =
false;
345 for (
int i = 0; i < static_cast<int>(_entries.size()); ++i) {
346 if (_passesFilter(_entries[i])) {
347 _filteredIndices.push_back(i);
358 _pendingEntries.clear();
362 _filteredIndices.clear();
363 _filterBypassed = !_hasActiveFilter();
367 _categoriesSet.clear();
368 _invalidateCategoryCache();
372void LogModel::_invalidateCategoryCache()
374 _categoriesDirty =
true;
379 if (_categoriesDirty) {
380 _categoriesCache = QStringList(_categoriesSet.begin(), _categoriesSet.end());
381 _categoriesCache.sort();
382 _categoriesDirty =
false;
384 return _categoriesCache;
390 if (_filterBypassed) {
391 return QList<LogEntry>(_entries.begin(), _entries.end());
394 QList<LogEntry> result;
395 result.reserve(
static_cast<int>(_filteredIndices.size()));
396 for (
const int idx : _filteredIndices) {
397 result.append(_entries[idx]);
#define QGC_LOGGING_CATEGORY(name, categoryStr)
Base class for table models that display LogEntry data.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const override
const LogEntry * entryAt(int row) const override
Return entry at visible row, or nullptr if out of range.
void enqueue(LogEntry entry)
void multiData(const QModelIndex &index, QModelRoleDataSpan roleDataSpan) const override
void setFilterText(const QString &text)
void setFilterRegex(bool enabled)
Q_INVOKABLE void clearFilters()
void filterCategoryChanged()
void setFilterCategory(const QString &category)
void setMaxEntries(int max)
void filterRegexValidChanged()
QList< LogEntry > filteredEntries() const
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
Q_INVOKABLE void setFilterTextDeferred(const QString &text)
void setFilterLevel(int level)
void filterRegexChanged()
QStringList categoriesList() const
void filterLevelChanged()
int rowCount(const QModelIndex &parent=QModelIndex()) const override
static SettingsManager * instance()
AppSettings * appSettings() const
QVariant columnDisplayData(int column) const
QVariant roleData(int role) const