• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

paulmthompson / WhiskerToolbox / 17826420298

18 Sep 2025 02:24AM UTC coverage: 71.942% (-0.002%) from 71.944%
17826420298

push

github

paulmthompson
Merge branch 'main' of https://github.com/paulmthompson/WhiskerToolbox

110 of 158 new or added lines in 12 files covered. (69.62%)

222 existing lines in 9 files now uncovered.

39625 of 55079 relevant lines covered (71.94%)

1301.53 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.89
/src/WhiskerToolbox/TableViewerWidget/PaginatedTableModel.cpp
1
#include "PaginatedTableModel.hpp"
2

3
#include "DataManager/DataManager.hpp"
4
#include "DataManager/utils/TableView/TableInfo.hpp"
5
#include "DataManager/utils/TableView/TableRegistry.hpp"
6
#include "DataManager/utils/TableView/adapters/DataManagerExtension.h"
7
#include "DataManager/utils/TableView/core/TableView.h"
8
#include "DataManager/utils/TableView/core/TableViewBuilder.h"
9
#include "DataManager/utils/TableView/interfaces/IRowSelector.h"
10
// removed duplicate include
11

12
#include <QDebug>
13
#include <algorithm>
14
#include <iomanip>
15
#include <sstream>
16

17
PaginatedTableModel::PaginatedTableModel(QObject * parent)
29✔
18
    : QAbstractTableModel(parent) {
29✔
19
}
29✔
20

21
PaginatedTableModel::~PaginatedTableModel() = default;
58✔
22

23
void PaginatedTableModel::setSourceTable(std::unique_ptr<IRowSelector> row_selector,
22✔
24
                                         std::vector<ColumnInfo> column_infos,
25
                                         std::shared_ptr<DataManager> data_manager) {
26
    beginResetModel();
22✔
27

28
    _source_row_selector = std::move(row_selector);
22✔
29
    _column_infos = std::move(column_infos);
22✔
30
    _data_manager = std::move(data_manager);
22✔
31
    _complete_table_view.reset();
22✔
32

33
    // Calculate total rows and extract column names
34
    if (_source_row_selector) {
22✔
35
        _total_rows = _source_row_selector->getRowCount();
22✔
36
    } else {
UNCOV
37
        _total_rows = 0;
×
38
    }
39

40
    _column_names.clear();
22✔
41
    for (auto const & col_info: _column_infos) {
70✔
42
        _column_names.push_back(QString::fromStdString(col_info.name));
48✔
43
    }
44

45
    // Clear cache
46
    _page_cache.clear();
22✔
47

48
    endResetModel();
22✔
49
}
22✔
50

51
void PaginatedTableModel::setTableView(std::shared_ptr<TableView> table_view) {
7✔
52
    beginResetModel();
7✔
53

54
    _complete_table_view = std::move(table_view);
7✔
55
    _source_row_selector.reset();
7✔
56
    _column_infos.clear();
7✔
57
    _data_manager.reset();
7✔
58

59
    if (_complete_table_view) {
7✔
60
        _total_rows = _complete_table_view->getRowCount();
7✔
61
        auto names = _complete_table_view->getColumnNames();
7✔
62
        _column_names.clear();
7✔
63
        for (auto const & name: names) {
34✔
64
            _column_names.push_back(QString::fromStdString(name));
27✔
65
        }
66
    } else {
7✔
UNCOV
67
        _total_rows = 0;
×
68
        _column_names.clear();
×
69
    }
70

71
    // Clear cache
72
    _page_cache.clear();
7✔
73

74
    endResetModel();
7✔
75
}
7✔
76

77
void PaginatedTableModel::clearTable() {
176✔
78
    beginResetModel();
176✔
79
    _source_row_selector.reset();
176✔
80
    _column_infos.clear();
176✔
81
    _data_manager.reset();
176✔
82
    _complete_table_view.reset();
176✔
83
    _total_rows = 0;
176✔
84
    _column_names.clear();
176✔
85
    _page_cache.clear();
176✔
86
    endResetModel();
176✔
87
}
176✔
88

89
void PaginatedTableModel::setPageSize(size_t page_size) {
33✔
90
    if (page_size == 0 || page_size == _page_size) return;
33✔
91

92
    beginResetModel();
33✔
93
    _page_size = page_size;
33✔
94
    _page_cache.clear();// Clear cache since page boundaries changed
33✔
95
    endResetModel();
33✔
96
}
97

98
int PaginatedTableModel::rowCount(QModelIndex const & parent) const {
700✔
99
    if (parent.isValid()) return 0;
700✔
100
    return static_cast<int>(_total_rows);
700✔
101
}
102

103
int PaginatedTableModel::columnCount(QModelIndex const & parent) const {
822✔
104
    if (parent.isValid()) return 0;
822✔
105
    return static_cast<int>(_column_names.size());
822✔
106
}
107

108
QVariant PaginatedTableModel::data(QModelIndex const & index, int role) const {
322✔
109
    if (role != Qt::DisplayRole || !index.isValid()) return {};
322✔
110
    if (index.row() < 0 || index.column() < 0) return {};
118✔
111
    if (index.row() >= static_cast<int>(_total_rows)) return {};
118✔
112
    if (index.column() >= _column_names.size()) return {};
118✔
113

114
    auto const row = static_cast<size_t>(index.row());
118✔
115
    auto const column_name = _column_names[index.column()].toStdString();
118✔
116

117
    try {
118
        if (_complete_table_view) {
118✔
119
            // Use the complete table view directly
120
            return formatValue(_complete_table_view, column_name, row);
26✔
121
        } else {
122
            // Use pagination with mini tables
123
            auto [mini_table, local_row] = getMiniTableForRow(row);
92✔
124
            if (!mini_table || local_row >= mini_table->getRowCount()) {
92✔
UNCOV
125
                return QString("Error");
×
126
            }
127
            return formatValue(mini_table, column_name, local_row);
92✔
128
        }
92✔
UNCOV
129
    } catch (std::exception const & e) {
×
130
        qDebug() << "Error accessing data at row" << row << "column" << column_name.c_str() << ":" << e.what();
×
131
        return QString("Error");
×
132
    }
×
133
}
118✔
134

135
QVariant PaginatedTableModel::headerData(int section, Qt::Orientation orientation, int role) const {
4,973✔
136
    if (role != Qt::DisplayRole) return {};
4,973✔
137
    if (orientation == Qt::Horizontal) {
1,277✔
138
        if (section >= 0 && section < _column_names.size()) {
164✔
139
            return _column_names[section];
164✔
140
        }
UNCOV
141
        return {};
×
142
    }
143
    return section + 1;// 1-based row numbering
1,113✔
144
}
145

146
std::pair<std::shared_ptr<TableView>, size_t> PaginatedTableModel::getMiniTableForRow(size_t row_index) const {
92✔
147
    if (!_source_row_selector || !_data_manager) {
92✔
UNCOV
148
        return {nullptr, 0};
×
149
    }
150

151
    // Calculate which page this row belongs to
152
    size_t const page_number = row_index / _page_size;
92✔
153
    size_t const page_start_row = page_number * _page_size;
92✔
154
    size_t const local_row = row_index - page_start_row;
92✔
155

156
    // Check cache first
157
    auto cache_it = _page_cache.find(page_number);
92✔
158
    if (cache_it != _page_cache.end()) {
92✔
159
        return {cache_it->second, local_row};
77✔
160
    }
161

162
    // Create mini table for this page
163
    size_t const actual_page_size = std::min(_page_size, _total_rows - page_start_row);
15✔
164
    auto mini_table = createMiniTable(page_start_row, actual_page_size);
15✔
165

166
    if (mini_table) {
15✔
167
        // Cache the mini table
168
        _page_cache[page_number] = mini_table;
15✔
169
        // Diagnostics: track number of pages materialized
170
        ++_materialized_page_count;
15✔
171
        cleanupCache();
15✔
172
    }
173

174
    return {mini_table, local_row};
15✔
175
}
15✔
176

177
std::shared_ptr<TableView> PaginatedTableModel::createMiniTable(size_t page_start_row, size_t page_size) const {
15✔
178
    if (!_source_row_selector || !_data_manager) {
15✔
UNCOV
179
        return nullptr;
×
180
    }
181

182
    try {
183
        // Get the data manager extension and table registry
184
        auto * table_registry = _data_manager->getTableRegistry();
15✔
185
        if (!table_registry) {
15✔
UNCOV
186
            qDebug() << "Failed to get table registry from data manager";
×
187
            return nullptr;
×
188
        }
189

190
        auto data_manager_extension = table_registry->getDataManagerExtension();
15✔
191
        if (!data_manager_extension) {
15✔
UNCOV
192
            qDebug() << "Failed to get data manager extension from table registry";
×
193
            return nullptr;
×
194
        }
195
        // Create a vector of indices for this window
196
        std::vector<size_t> window_indices;
15✔
197
        window_indices.reserve(page_size);
15✔
198
        for (size_t i = 0; i < page_size && (page_start_row + i) < _total_rows; ++i) {
914✔
199
            window_indices.push_back(page_start_row + i);
899✔
200
        }
201

202
        // Use the existing cloneRowSelectorFiltered pattern
203
        auto windowed_selector = [&]() -> std::unique_ptr<IRowSelector> {
30✔
204
            // Create filtered selector based on the type of the source selector
205
            if (auto indexSelector = dynamic_cast<IndexSelector const *>(_source_row_selector.get())) {
15✔
UNCOV
206
                std::vector<size_t> const & source_indices = indexSelector->getIndices();
×
207
                std::vector<size_t> filtered;
×
208
                filtered.reserve(window_indices.size());
×
NEW
209
                for (size_t const k: window_indices) {
×
210
                    if (k < source_indices.size()) {
×
211
                        filtered.push_back(source_indices[k]);
×
212
                    }
213
                }
UNCOV
214
                return std::make_unique<IndexSelector>(std::move(filtered));
×
215
            }
×
216

217
            if (auto timestampSelector = dynamic_cast<TimestampSelector const *>(_source_row_selector.get())) {
15✔
218
                auto const & timestamps = timestampSelector->getTimestamps();
7✔
219
                std::vector<TimeFrameIndex> filtered;
7✔
220
                filtered.reserve(window_indices.size());
7✔
221
                for (size_t const k: window_indices) {
455✔
222
                    if (k < timestamps.size()) {
448✔
223
                        filtered.push_back(timestamps[k]);
448✔
224
                    }
225
                }
226
                return std::make_unique<TimestampSelector>(std::move(filtered), timestampSelector->getTimeFrame());
7✔
227
            }
7✔
228

229
            if (auto intervalSelector = dynamic_cast<IntervalSelector const *>(_source_row_selector.get())) {
8✔
230
                auto const & intervals = intervalSelector->getIntervals();
8✔
231
                std::vector<TimeFrameInterval> filtered;
8✔
232
                filtered.reserve(window_indices.size());
8✔
233
                for (size_t const k: window_indices) {
459✔
234
                    if (k < intervals.size()) {
451✔
235
                        filtered.push_back(intervals[k]);
451✔
236
                    }
237
                }
238
                return std::make_unique<IntervalSelector>(std::move(filtered), intervalSelector->getTimeFrame());
8✔
239
            }
8✔
240

UNCOV
241
            return nullptr;
×
242
        }();
15✔
243

244
        if (!windowed_selector) {
15✔
UNCOV
245
            qDebug() << "Failed to create windowed selector for page starting at row" << page_start_row;
×
246
            return nullptr;
×
247
        }
248

249
        // Build a mini table with the windowed selector
250
        TableViewBuilder builder(data_manager_extension);
15✔
251
        builder.setRowSelector(std::move(windowed_selector));
15✔
252

253
        // Add all columns using the TableRegistry method
254
        for (auto const & col_info: _column_infos) {
93✔
255
            if (!table_registry->addColumnToBuilder(builder, col_info)) {
78✔
UNCOV
256
                qDebug() << "Failed to add column" << col_info.name.c_str() << "to mini table";
×
257
                return nullptr;
×
258
            }
259
        }
260

261
        auto mini_table_obj = builder.build();
15✔
262
        auto mini_table = std::make_shared<TableView>(std::move(mini_table_obj));
15✔
263
        mini_table->materializeAll();
15✔
264

265
        return mini_table;
15✔
266
    } catch (std::exception const & e) {
15✔
UNCOV
267
        qDebug() << "Exception creating mini table:" << e.what();
×
268
        return nullptr;
×
269
    }
×
270
}
271

272
void PaginatedTableModel::cleanupCache() const {
15✔
273
    while (_page_cache.size() > MAX_CACHED_PAGES) {
15✔
274
        // Remove the first (oldest) entry
UNCOV
275
        _page_cache.erase(_page_cache.begin());
×
276
    }
277
}
15✔
278

279
QString PaginatedTableModel::formatValue(std::shared_ptr<TableView> const & mini_table,
118✔
280
                                         std::string const & column_name,
281
                                         size_t local_row) {
282
    if (!mini_table || local_row >= mini_table->getRowCount()) {
118✔
UNCOV
283
        return {"N/A"};
×
284
    }
285

286
    try {
287
        return mini_table->visitColumnData(column_name, [local_row](auto const & vec) -> QString {
236✔
288
            using VecT = std::decay_t<decltype(vec)>;
289
            using ElemT = typename VecT::value_type;
290

291
            if (local_row >= vec.size()) {
118✔
UNCOV
292
                if constexpr (std::is_same_v<ElemT, double>) return {"NaN"};
×
293
                if constexpr (std::is_same_v<ElemT, int>) return {"NaN"};
×
294
                if constexpr (std::is_same_v<ElemT, long long>) return {"NaN"};
UNCOV
295
                if constexpr (std::is_same_v<ElemT, bool>) return {"false"};
×
296
                return {"N/A"};
×
297
            }
298

299
            if constexpr (std::is_same_v<ElemT, double>) {
300
                return QString::number(vec[local_row], 'f', 3);
44✔
301
            } else if constexpr (std::is_same_v<ElemT, int>) {
302
                return QString::number(vec[local_row]);
26✔
303
            } else if constexpr (std::is_same_v<ElemT, int64_t>) {
304
                return QString::number(static_cast<int64_t>(vec[local_row]));
3✔
305
            } else if constexpr (std::is_same_v<ElemT, bool>) {
306
                return vec[local_row] ? QStringLiteral("true") : QStringLiteral("false");
78✔
307
            } else if constexpr (
308
                    std::is_same_v<ElemT, std::vector<double>> ||
309
                    std::is_same_v<ElemT, std::vector<int>> ||
310
                    std::is_same_v<ElemT, std::vector<float>>) {
311
                return joinVector(vec[local_row]);
6✔
312
            } else {
313
                return {"?"};
×
314
            }
315
        });
118✔
316
    } catch (...) {
×
317
        return {"Error"};
×
318
    }
×
319
}
320

321
template<typename T>
322
QString PaginatedTableModel::joinVector(std::vector<T> const & values) {
6✔
323
    if (values.empty()) return QString();
6✔
324
    QString out;
6✔
325
    out.reserve(static_cast<int>(values.size() * 4));
6✔
326
    for (size_t i = 0; i < values.size(); ++i) {
21✔
327
        if constexpr (std::is_same_v<T, double>) {
328
            out += QString::number(values[i], 'f', 3);
6✔
329
        } else if constexpr (std::is_same_v<T, float>) {
330
            out += QString::number(static_cast<double>(values[i]), 'f', 3);
9✔
331
        } else {
332
            out += QString::number(values[i]);
×
333
        }
334
        if (i + 1 < values.size()) out += ",";
15✔
335
    }
336
    return out;
6✔
337
}
6✔
338

339
// Explicit instantiations
340
template QString PaginatedTableModel::joinVector<double>(std::vector<double> const &);
341
template QString PaginatedTableModel::joinVector<int>(std::vector<int> const &);
342
template QString PaginatedTableModel::joinVector<float>(std::vector<float> const &);
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc