9 _batchTimer.setSingleShot(
true);
10 (void)connect(&_batchTimer, &QChronoTimer::timeout,
this, &LogModel::_flushPending);
12 _filterTextDebounce.setInterval(kFilterDebounceMs);
13 _filterTextDebounce.setSingleShot(
true);
14 connect(&_filterTextDebounce, &QTimer::timeout,
this, [
this]() {
15 if (_filterText != _pendingFilterText) {
16 _filterText = _pendingFilterText;
17 emit filterTextChanged();
21 _rebuildFilteredIndices();
26int LogModel::rowCount(
const QModelIndex& parent)
const
28 if (parent.isValid()) {
31 return _filterBypassed ?
static_cast<int>(_entries.size())
32 : static_cast<int>(_filteredIndices.size());
41 if (_filterBypassed) {
42 if (row >=
static_cast<int>(_entries.size())) {
45 return &_entries[row];
48 if (row >=
static_cast<int>(_filteredIndices.size())) {
51 return &_entries[_filteredIndices[row]];
54void LogModel::setMaxEntries(
int max)
56 max = qMax(1000, max);
57 if (_maxEntries != max) {
63void LogModel::setFilterLevel(
int level)
65 if (_filterLevel != level) {
68 _rebuildFilteredIndices();
72void LogModel::setFilterCategory(
const QString& category)
74 if (_filterCategory != category) {
75 _filterCategory = category;
77 _rebuildFilteredIndices();
81void LogModel::setFilterText(
const QString& text)
83 if (_filterText != text) {
84 _pendingFilterText = text;
85 _filterTextDebounce.stop();
91 _rebuildFilteredIndices();
95void LogModel::setFilterTextDeferred(
const QString& text)
97 if (_pendingFilterText != text) {
98 _pendingFilterText = text;
99 _filterTextDebounce.start();
103void LogModel::setFilterRegex(
bool enabled)
105 if (_filterRegex != enabled) {
106 _filterRegex = enabled;
108 if (enabled && !_filterText.isEmpty()) {
113 _rebuildFilteredIndices();
117void LogModel::clearFilters()
119 bool changed =
false;
120 _filterTextDebounce.stop();
121 _pendingFilterText.clear();
123 if (_filterLevel != LogEntry::Debug) {
124 _filterLevel = LogEntry::Debug;
128 if (!_filterCategory.isEmpty()) {
129 _filterCategory.clear();
133 if (!_filterText.isEmpty()) {
139 _filterRegex =
false;
145 _rebuildFilteredIndices();
149bool LogModel::_hasActiveFilter()
const
151 return _filterLevel > LogEntry::Debug || !_filterCategory.isEmpty() || !_filterText.isEmpty();
154bool LogModel::_passesFilter(
const LogEntry& entry)
const
156 if (
static_cast<int>(entry.
level) < _filterLevel) {
159 if (!_filterCategory.isEmpty() && !entry.
category.contains(_filterCategory, Qt::CaseInsensitive)) {
162 if (!_filterText.isEmpty()) {
164 if (!_compiledRegex.isValid() || !_compiledRegex.match(entry.
message).hasMatch()) {
168 if (!entry.
message.contains(_filterText, Qt::CaseInsensitive)) {
176void LogModel::_recompileRegex()
178 _compiledRegex = QRegularExpression(_filterText, QRegularExpression::CaseInsensitiveOption);
182void LogModel::enqueue(
LogEntry entry)
184 _pendingEntries.push_back(std::move(entry));
185 if (
static_cast<int>(_pendingEntries.size()) >= kBatchMaxSize) {
188 }
else if (!_batchTimer.isActive()) {
193void LogModel::_flushPending()
197 if (_pendingEntries.empty()) {
203 bool newCategories =
false;
204 for (
auto& entry : _pendingEntries) {
205 if (!entry.
category.isEmpty() && !_categoriesSet.contains(entry.
category)) {
206 _categoriesSet.insert(entry.
category);
207 newCategories =
true;
211 const int overflow =
static_cast<int>(_entries.size() + _pendingEntries.size()) - _maxEntries;
212 if (overflow > 0 && overflow <=
static_cast<int>(_pendingEntries.size())) {
213 _pendingEntries.erase(_pendingEntries.begin(), _pendingEntries.begin() + overflow);
216 const int firstNew =
static_cast<int>(_entries.size());
217 for (
auto& entry : _pendingEntries) {
218 _entries.push_back(std::move(entry));
220 _pendingEntries.clear();
222 const int lastNew =
static_cast<int>(_entries.size()) - 1;
223 if (firstNew <= lastNew) {
224 _appendToFiltered(firstNew, lastNew);
230 _invalidateCategoryCache();
235void LogModel::_appendToFiltered(
int first,
int last)
237 if (_filterBypassed) {
239 beginInsertRows(QModelIndex(), first, last);
244 std::vector<int> newIndices;
245 for (
int i = first; i <= last; ++i) {
246 if (_passesFilter(_entries[i])) {
247 newIndices.push_back(i);
251 if (!newIndices.empty()) {
252 const int proxyFirst =
static_cast<int>(_filteredIndices.size());
253 beginInsertRows(QModelIndex(), proxyFirst, proxyFirst +
static_cast<int>(newIndices.size()) - 1);
254 _filteredIndices.insert(_filteredIndices.end(), newIndices.begin(), newIndices.end());
259void LogModel::_trimExcess()
261 const int total =
static_cast<int>(_entries.size()) +
static_cast<int>(_pendingEntries.size());
262 if (total <= _maxEntries) {
266 const int removeCount = total - _maxEntries;
267 const int actualRemove = qMin(removeCount,
static_cast<int>(_entries.size()));
268 if (actualRemove <= 0) {
272 if (_filterBypassed) {
273 beginRemoveRows(QModelIndex(), 0, actualRemove - 1);
274 _entries.erase(_entries.begin(), _entries.begin() + actualRemove);
279 int filteredRemoved = 0;
280 while (filteredRemoved <
static_cast<int>(_filteredIndices.size()) &&
281 _filteredIndices[filteredRemoved] < actualRemove) {
285 if (filteredRemoved > 0) {
286 beginRemoveRows(QModelIndex(), 0, filteredRemoved - 1);
287 _filteredIndices.erase(_filteredIndices.begin(), _filteredIndices.begin() + filteredRemoved);
291 _entries.erase(_entries.begin(), _entries.begin() + actualRemove);
293 for (
auto& idx : _filteredIndices) {
298void LogModel::_rebuildFilteredIndices()
301 _filteredIndices.clear();
303 if (!_hasActiveFilter()) {
304 _filterBypassed =
true;
306 _filterBypassed =
false;
307 for (
int i = 0; i < static_cast<int>(_entries.size()); ++i) {
308 if (_passesFilter(_entries[i])) {
309 _filteredIndices.push_back(i);
317void LogModel::clear()
320 _pendingEntries.clear();
324 _filteredIndices.clear();
325 _filterBypassed = !_hasActiveFilter();
329 _categoriesSet.clear();
330 _invalidateCategoryCache();
334void LogModel::_invalidateCategoryCache()
336 _categoriesDirty =
true;
339QStringList LogModel::categoriesList()
const
341 if (_categoriesDirty) {
342 _categoriesCache = QStringList(_categoriesSet.begin(), _categoriesSet.end());
343 _categoriesCache.sort();
344 _categoriesDirty =
false;
346 return _categoriesCache;
350QList<LogEntry> LogModel::filteredEntries()
const
352 if (_filterBypassed) {
353 return QList<LogEntry>(_entries.begin(), _entries.end());
356 QList<LogEntry> result;
357 result.reserve(
static_cast<int>(_filteredIndices.size()));
358 for (
const int idx : _filteredIndices) {
359 result.append(_entries[idx]);
#define QGC_LOGGING_CATEGORY(name, categoryStr)
const LogEntry * entryAt(int row) const override
Return entry at visible row, or nullptr if out of range.
void filterCategoryChanged()
void filterRegexValidChanged()
void filterRegexChanged()
void filterLevelChanged()