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

paulmthompson / WhiskerToolbox / 18050758459

26 Sep 2025 09:57PM UTC coverage: 69.825% (+0.1%) from 69.725%
18050758459

push

github

paulmthompson
multi computer tables show up now

192 of 242 new or added lines in 5 files covered. (79.34%)

22 existing lines in 3 files now uncovered.

43180 of 61840 relevant lines covered (69.83%)

1137.92 hits per line

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

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

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

14
#include <QDebug>
15
#include <algorithm>
16
#include <iomanip>
17
#include <sstream>
18

19
PaginatedTableModel::PaginatedTableModel(QObject * parent)
31✔
20
    : QAbstractTableModel(parent) {
31✔
21
}
31✔
22

23
PaginatedTableModel::~PaginatedTableModel() = default;
62✔
24

25
void PaginatedTableModel::setSourceTable(std::unique_ptr<IRowSelector> row_selector,
25✔
26
                                         std::vector<ColumnInfo> column_infos,
27
                                         std::shared_ptr<DataManager> data_manager,
28
                                         QString row_source) {
29
    beginResetModel();
25✔
30

31
    _source_row_selector = std::move(row_selector);
25✔
32
    _column_infos = std::move(column_infos);
25✔
33
    _data_manager = std::move(data_manager);
25✔
34
    _row_source = row_source;
25✔
35
    _complete_table_view.reset();
25✔
36

37
    // Calculate total rows and extract column names
38
    if (_source_row_selector) {
25✔
39
        _total_rows = _source_row_selector->getRowCount();
25✔
40
    } else {
41
        _total_rows = 0;
×
42
    }
43

44
    // Build the TableView to get the actual column names (especially for multi-output computers)
45
    try {
46
        auto * reg = _data_manager->getTableRegistry();
25✔
47
        auto data_manager_extension = reg ? reg->getDataManagerExtension() : nullptr;
25✔
48
        if (data_manager_extension) {
25✔
49
            // Create the TableViewBuilder
50
            TableViewBuilder builder(data_manager_extension);
25✔
51
            
52
            // Create a new row selector from the row source
53
            if (!_row_source.isEmpty()) {
25✔
54
                // Recreate the row selector from the row source
55
                auto new_row_selector = createRowSelectorFromSource(_row_source);
20✔
56
                if (new_row_selector) {
20✔
57
                    builder.setRowSelector(std::move(new_row_selector));
20✔
58
                }
59
            }
20✔
60

61
            // Add all enabled columns from the tree
62
            bool all_columns_valid = true;
25✔
63
            for (auto const & column_info: _column_infos) {
76✔
64
                // Create a copy of column_info with cleaned name (strip "lines:" prefix)
65
                ColumnInfo cleaned_column_info = column_info;
51✔
66
                if (cleaned_column_info.name.starts_with("lines:")) {
51✔
NEW
67
                    cleaned_column_info.name = cleaned_column_info.name.substr(6); // Remove "lines:" prefix
×
68
                }
69
                
70
                if (!reg->addColumnToBuilder(builder, cleaned_column_info)) {
51✔
NEW
71
                    qDebug() << "Failed to create column:" << QString::fromStdString(cleaned_column_info.name);
×
NEW
72
                    all_columns_valid = false;
×
NEW
73
                    break;
×
74
                }
75
            }
51✔
76

77
            if (all_columns_valid) {
25✔
78
                // Build the table
79
                auto table_view = builder.build();
25✔
80
                
81
                // Use the actual column names from the built TableView
82
                auto actual_column_names = table_view.getColumnNames();
20✔
83
                _column_names.clear();
20✔
84
                for (auto const & name: actual_column_names) {
64✔
85
                    _column_names.push_back(QString::fromStdString(name));
44✔
86
                }
87
            } else {
20✔
88
                // Fallback to using column_infos names
NEW
89
                _column_names.clear();
×
NEW
90
                for (auto const & col_info: _column_infos) {
×
NEW
91
                    _column_names.push_back(QString::fromStdString(col_info.name));
×
92
                }
93
            }
94
        } else {
25✔
95
            // Fallback to using column_infos names
NEW
96
            _column_names.clear();
×
NEW
97
            for (auto const & col_info: _column_infos) {
×
NEW
98
                _column_names.push_back(QString::fromStdString(col_info.name));
×
99
            }
100
        }
101
    } catch (std::exception const & e) {
30✔
102
        qDebug() << "Exception during column name resolution:" << e.what();
5✔
103
        // Fallback to using column_infos names
104
        _column_names.clear();
5✔
105
        for (auto const & col_info: _column_infos) {
27✔
106
            _column_names.push_back(QString::fromStdString(col_info.name));
22✔
107
        }
108
    }
5✔
109

110
    // Clear cache
111
    _page_cache.clear();
25✔
112

113
    endResetModel();
25✔
114
}
25✔
115

116
std::unique_ptr<IRowSelector> PaginatedTableModel::createRowSelectorFromSource(QString const & row_source) const {
20✔
117
    // Parse the row source to get type and name
118
    QString source_type;
20✔
119
    QString source_name;
20✔
120

121
    if (row_source.startsWith("TimeFrame: ")) {
20✔
122
        source_type = "TimeFrame";
3✔
123
        source_name = row_source.mid(11);// Remove "TimeFrame: " prefix
3✔
124
    } else if (row_source.startsWith("Events: ")) {
17✔
NEW
125
        source_type = "Events";
×
NEW
126
        source_name = row_source.mid(8);// Remove "Events: " prefix
×
127
    } else if (row_source.startsWith("Intervals: ")) {
17✔
128
        source_type = "Intervals";
17✔
129
        source_name = row_source.mid(11);// Remove "Intervals: " prefix
17✔
130
    } else {
NEW
131
        qDebug() << "Unknown row source format:" << row_source;
×
NEW
132
        return nullptr;
×
133
    }
134

135
    auto const source_name_str = source_name.toStdString();
20✔
136

137
    try {
138
        if (source_type == "TimeFrame") {
20✔
139
            // Create TimestampSelector using TimeFrame
140
            auto timeframe = _data_manager->getTime(TimeKey(source_name_str));
3✔
141
            if (!timeframe) {
3✔
NEW
142
                qDebug() << "TimeFrame not found:" << source_name;
×
NEW
143
                return nullptr;
×
144
            }
145

146
            // Use timestamps to select all rows
147
            std::vector<TimeFrameIndex> timestamps;
3✔
148
            for (int64_t i = 0; i < timeframe->getTotalFrameCount(); ++i) {
306✔
149
                timestamps.push_back(TimeFrameIndex(i));
303✔
150
            }
151
            return std::make_unique<TimestampSelector>(std::move(timestamps), timeframe);
3✔
152

153
        } else if (source_type == "Events") {
20✔
154
            // Create TimestampSelector using DigitalEventSeries
NEW
155
            auto event_series = _data_manager->getData<DigitalEventSeries>(source_name_str);
×
NEW
156
            if (!event_series) {
×
NEW
157
                qDebug() << "DigitalEventSeries not found:" << source_name;
×
NEW
158
                return nullptr;
×
159
            }
160

NEW
161
            auto events = event_series->getEventSeries();
×
NEW
162
            auto timeframe_key = _data_manager->getTimeKey(source_name_str);
×
NEW
163
            auto timeframe_obj = _data_manager->getTime(timeframe_key);
×
NEW
164
            if (!timeframe_obj) {
×
NEW
165
                qDebug() << "TimeFrame not found for events:" << timeframe_key.str();
×
NEW
166
                return nullptr;
×
167
            }
168

169
            // Convert events to TimeFrameIndex
NEW
170
            std::vector<TimeFrameIndex> timestamps;
×
NEW
171
            for (auto const & event: events) {
×
NEW
172
                timestamps.push_back(TimeFrameIndex(static_cast<int64_t>(event)));
×
173
            }
174

NEW
175
            return std::make_unique<TimestampSelector>(std::move(timestamps), timeframe_obj);
×
176

177
        } else if (source_type == "Intervals") {
17✔
178
            // Create IntervalSelector using DigitalIntervalSeries
179
            auto interval_series = _data_manager->getData<DigitalIntervalSeries>(source_name_str);
17✔
180
            if (!interval_series) {
17✔
NEW
181
                qDebug() << "DigitalIntervalSeries not found:" << source_name;
×
NEW
182
                return nullptr;
×
183
            }
184

185
            auto intervals = interval_series->getDigitalIntervalSeries();
17✔
186
            auto timeframe_key = _data_manager->getTimeKey(source_name_str);
17✔
187
            auto timeframe_obj = _data_manager->getTime(timeframe_key);
17✔
188
            if (!timeframe_obj) {
17✔
NEW
189
                qDebug() << "TimeFrame not found for intervals:" << timeframe_key.str();
×
NEW
190
                return nullptr;
×
191
            }
192

193
            // Create intervals (using default capture range of 30000)
194
            int capture_range = 30000;
17✔
195
            bool use_beginning = true; // Default to beginning
17✔
196
            bool use_interval_itself = false; // Default to not using interval itself
17✔
197

198
            std::vector<TimeFrameInterval> tf_intervals;
17✔
199
            for (auto const & interval: intervals) {
84✔
200
                if (use_interval_itself) {
67✔
201
                    // Use the interval as-is
NEW
202
                    tf_intervals.emplace_back(TimeFrameIndex(interval.start), TimeFrameIndex(interval.end));
×
203
                } else {
204
                    // Determine the reference point (beginning or end of interval)
205
                    int64_t reference_point;
206
                    if (use_beginning) {
67✔
207
                        reference_point = interval.start;
67✔
208
                    } else {
NEW
209
                        reference_point = interval.end;
×
210
                    }
211

212
                    // Create a new interval around the reference point
213
                    int64_t start_point = reference_point - capture_range;
67✔
214
                    int64_t end_point = reference_point + capture_range;
67✔
215

216
                    // Ensure bounds are within the timeframe
217
                    start_point = std::max(start_point, int64_t(0));
67✔
218
                    end_point = std::min(end_point, static_cast<int64_t>(timeframe_obj->getTotalFrameCount() - 1));
67✔
219

220
                    tf_intervals.emplace_back(TimeFrameIndex(start_point), TimeFrameIndex(end_point));
67✔
221
                }
222
            }
223

224
            return std::make_unique<IntervalSelector>(std::move(tf_intervals), timeframe_obj);
17✔
225
        }
17✔
226

NEW
227
    } catch (std::exception const & e) {
×
NEW
228
        qDebug() << "Exception creating row selector:" << e.what();
×
NEW
229
        return nullptr;
×
NEW
230
    }
×
231

NEW
232
    qDebug() << "Unsupported row source type:" << source_type;
×
NEW
233
    return nullptr;
×
234
}
20✔
235

236
void PaginatedTableModel::setTableView(std::shared_ptr<TableView> table_view) {
7✔
237
    beginResetModel();
7✔
238

239
    _complete_table_view = std::move(table_view);
7✔
240
    _source_row_selector.reset();
7✔
241
    _column_infos.clear();
7✔
242
    _data_manager.reset();
7✔
243

244
    if (_complete_table_view) {
7✔
245
        _total_rows = _complete_table_view->getRowCount();
7✔
246
        auto names = _complete_table_view->getColumnNames();
7✔
247
        _column_names.clear();
7✔
248
        for (auto const & name: names) {
34✔
249
            _column_names.push_back(QString::fromStdString(name));
27✔
250
        }
251
    } else {
7✔
252
        _total_rows = 0;
×
253
        _column_names.clear();
×
254
    }
255

256
    // Clear cache
257
    _page_cache.clear();
7✔
258

259
    endResetModel();
7✔
260
}
7✔
261

262
void PaginatedTableModel::clearTable() {
193✔
263
    beginResetModel();
193✔
264
    _source_row_selector.reset();
193✔
265
    _column_infos.clear();
193✔
266
    _data_manager.reset();
193✔
267
    _complete_table_view.reset();
193✔
268
    _total_rows = 0;
193✔
269
    _column_names.clear();
193✔
270
    _page_cache.clear();
193✔
271
    endResetModel();
193✔
272
}
193✔
273

274
void PaginatedTableModel::setPageSize(size_t page_size) {
35✔
275
    if (page_size == 0 || page_size == _page_size) return;
35✔
276

277
    beginResetModel();
35✔
278
    _page_size = page_size;
35✔
279
    _page_cache.clear();// Clear cache since page boundaries changed
35✔
280
    endResetModel();
35✔
281
}
282

283
int PaginatedTableModel::rowCount(QModelIndex const & parent) const {
748✔
284
    if (parent.isValid()) return 0;
748✔
285
    return static_cast<int>(_total_rows);
748✔
286
}
287

288
int PaginatedTableModel::columnCount(QModelIndex const & parent) const {
878✔
289
    if (parent.isValid()) return 0;
878✔
290
    return static_cast<int>(_column_names.size());
878✔
291
}
292

293
QVariant PaginatedTableModel::data(QModelIndex const & index, int role) const {
322✔
294
    if (role != Qt::DisplayRole || !index.isValid()) return {};
322✔
295
    if (index.row() < 0 || index.column() < 0) return {};
118✔
296
    if (index.row() >= static_cast<int>(_total_rows)) return {};
118✔
297
    if (index.column() >= _column_names.size()) return {};
118✔
298

299
    auto const row = static_cast<size_t>(index.row());
118✔
300
    auto const column_name = _column_names[index.column()].toStdString();
118✔
301

302
    try {
303
        if (_complete_table_view) {
118✔
304
            // Use the complete table view directly
305
            return formatValue(_complete_table_view, column_name, row);
26✔
306
        } else {
307
            // Use pagination with mini tables
308
            auto [mini_table, local_row] = getMiniTableForRow(row);
92✔
309
            if (!mini_table || local_row >= mini_table->getRowCount()) {
92✔
310
                return QString("Error");
×
311
            }
312
            return formatValue(mini_table, column_name, local_row);
92✔
313
        }
92✔
314
    } catch (std::exception const & e) {
×
315
        qDebug() << "Error accessing data at row" << row << "column" << column_name.c_str() << ":" << e.what();
×
316
        return QString("Error");
×
317
    }
×
318
}
118✔
319

320
QVariant PaginatedTableModel::headerData(int section, Qt::Orientation orientation, int role) const {
5,437✔
321
    if (role != Qt::DisplayRole) return {};
5,437✔
322
    if (orientation == Qt::Horizontal) {
1,420✔
323
        if (section >= 0 && section < _column_names.size()) {
206✔
324
            return _column_names[section];
206✔
325
        }
326
        return {};
×
327
    }
328
    return section + 1;// 1-based row numbering
1,214✔
329
}
330

331
std::pair<std::shared_ptr<TableView>, size_t> PaginatedTableModel::getMiniTableForRow(size_t row_index) const {
92✔
332
    if (!_source_row_selector || !_data_manager) {
92✔
333
        return {nullptr, 0};
×
334
    }
335

336
    // Calculate which page this row belongs to
337
    size_t const page_number = row_index / _page_size;
92✔
338
    size_t const page_start_row = page_number * _page_size;
92✔
339
    size_t const local_row = row_index - page_start_row;
92✔
340

341
    // Check cache first
342
    auto cache_it = _page_cache.find(page_number);
92✔
343
    if (cache_it != _page_cache.end()) {
92✔
344
        return {cache_it->second, local_row};
77✔
345
    }
346

347
    // Create mini table for this page
348
    size_t const actual_page_size = std::min(_page_size, _total_rows - page_start_row);
15✔
349
    auto mini_table = createMiniTable(page_start_row, actual_page_size);
15✔
350

351
    if (mini_table) {
15✔
352
        // Cache the mini table
353
        _page_cache[page_number] = mini_table;
15✔
354
        // Diagnostics: track number of pages materialized
355
        ++_materialized_page_count;
15✔
356
        cleanupCache();
15✔
357
    }
358

359
    return {mini_table, local_row};
15✔
360
}
15✔
361

362
std::shared_ptr<TableView> PaginatedTableModel::createMiniTable(size_t page_start_row, size_t page_size) const {
15✔
363
    if (!_source_row_selector || !_data_manager) {
15✔
364
        return nullptr;
×
365
    }
366

367
    try {
368
        // Get the data manager extension and table registry
369
        auto * table_registry = _data_manager->getTableRegistry();
15✔
370
        if (!table_registry) {
15✔
371
            qDebug() << "Failed to get table registry from data manager";
×
372
            return nullptr;
×
373
        }
374

375
        auto data_manager_extension = table_registry->getDataManagerExtension();
15✔
376
        if (!data_manager_extension) {
15✔
377
            qDebug() << "Failed to get data manager extension from table registry";
×
378
            return nullptr;
×
379
        }
380
        // Create a vector of indices for this window
381
        std::vector<size_t> window_indices;
15✔
382
        window_indices.reserve(page_size);
15✔
383
        for (size_t i = 0; i < page_size && (page_start_row + i) < _total_rows; ++i) {
914✔
384
            window_indices.push_back(page_start_row + i);
899✔
385
        }
386

387
        // Use the existing cloneRowSelectorFiltered pattern
388
        auto windowed_selector = [&]() -> std::unique_ptr<IRowSelector> {
30✔
389
            // Create filtered selector based on the type of the source selector
390
            if (auto indexSelector = dynamic_cast<IndexSelector const *>(_source_row_selector.get())) {
15✔
391
                std::vector<size_t> const & source_indices = indexSelector->getIndices();
×
392
                std::vector<size_t> filtered;
×
393
                filtered.reserve(window_indices.size());
×
394
                for (size_t const k: window_indices) {
×
395
                    if (k < source_indices.size()) {
×
396
                        filtered.push_back(source_indices[k]);
×
397
                    }
398
                }
399
                return std::make_unique<IndexSelector>(std::move(filtered));
×
400
            }
×
401

402
            if (auto timestampSelector = dynamic_cast<TimestampSelector const *>(_source_row_selector.get())) {
15✔
403
                auto const & timestamps = timestampSelector->getTimestamps();
7✔
404
                std::vector<TimeFrameIndex> filtered;
7✔
405
                filtered.reserve(window_indices.size());
7✔
406
                for (size_t const k: window_indices) {
455✔
407
                    if (k < timestamps.size()) {
448✔
408
                        filtered.push_back(timestamps[k]);
448✔
409
                    }
410
                }
411
                return std::make_unique<TimestampSelector>(std::move(filtered), timestampSelector->getTimeFrame());
7✔
412
            }
7✔
413

414
            if (auto intervalSelector = dynamic_cast<IntervalSelector const *>(_source_row_selector.get())) {
8✔
415
                auto const & intervals = intervalSelector->getIntervals();
8✔
416
                std::vector<TimeFrameInterval> filtered;
8✔
417
                filtered.reserve(window_indices.size());
8✔
418
                for (size_t const k: window_indices) {
459✔
419
                    if (k < intervals.size()) {
451✔
420
                        filtered.push_back(intervals[k]);
451✔
421
                    }
422
                }
423
                return std::make_unique<IntervalSelector>(std::move(filtered), intervalSelector->getTimeFrame());
8✔
424
            }
8✔
425

426
            return nullptr;
×
427
        }();
15✔
428

429
        if (!windowed_selector) {
15✔
430
            qDebug() << "Failed to create windowed selector for page starting at row" << page_start_row;
×
431
            return nullptr;
×
432
        }
433

434
        // Build a mini table with the windowed selector
435
        TableViewBuilder builder(data_manager_extension);
15✔
436
        builder.setRowSelector(std::move(windowed_selector));
15✔
437

438
        // Add all columns using the TableRegistry method
439
        for (auto const & col_info: _column_infos) {
93✔
440
            if (!table_registry->addColumnToBuilder(builder, col_info)) {
78✔
441
                qDebug() << "Failed to add column" << col_info.name.c_str() << "to mini table";
×
442
                return nullptr;
×
443
            }
444
        }
445

446
        auto mini_table_obj = builder.build();
15✔
447
        auto mini_table = std::make_shared<TableView>(std::move(mini_table_obj));
15✔
448
        mini_table->materializeAll();
15✔
449

450
        return mini_table;
15✔
451
    } catch (std::exception const & e) {
15✔
452
        qDebug() << "Exception creating mini table:" << e.what();
×
453
        return nullptr;
×
454
    }
×
455
}
456

457
void PaginatedTableModel::cleanupCache() const {
15✔
458
    while (_page_cache.size() > MAX_CACHED_PAGES) {
15✔
459
        // Remove the first (oldest) entry
460
        _page_cache.erase(_page_cache.begin());
×
461
    }
462
}
15✔
463

464
QString PaginatedTableModel::formatValue(std::shared_ptr<TableView> const & mini_table,
118✔
465
                                         std::string const & column_name,
466
                                         size_t local_row) {
467
    if (!mini_table || local_row >= mini_table->getRowCount()) {
118✔
468
        return {"N/A"};
×
469
    }
470

471
    try {
472
        return mini_table->visitColumnData(column_name, [local_row](auto const & vec) -> QString {
236✔
473
            using VecT = std::decay_t<decltype(vec)>;
474
            using ElemT = typename VecT::value_type;
475

476
            if (local_row >= vec.size()) {
118✔
477
                if constexpr (std::is_same_v<ElemT, double>) return {"NaN"};
×
478
                if constexpr (std::is_same_v<ElemT, int>) return {"NaN"};
×
479
                if constexpr (std::is_same_v<ElemT, long long>) return {"NaN"};
480
                if constexpr (std::is_same_v<ElemT, bool>) return {"false"};
×
481
                return {"N/A"};
×
482
            }
483

484
            if constexpr (std::is_same_v<ElemT, double>) {
485
                return QString::number(vec[local_row], 'f', 3);
44✔
486
            } else if constexpr (std::is_same_v<ElemT, int>) {
487
                return QString::number(vec[local_row]);
26✔
488
            } else if constexpr (std::is_same_v<ElemT, int64_t>) {
489
                return QString::number(static_cast<int64_t>(vec[local_row]));
3✔
490
            } else if constexpr (std::is_same_v<ElemT, bool>) {
491
                return vec[local_row] ? QStringLiteral("true") : QStringLiteral("false");
78✔
492
            } else if constexpr (
493
                    std::is_same_v<ElemT, std::vector<double>> ||
494
                    std::is_same_v<ElemT, std::vector<int>> ||
495
                    std::is_same_v<ElemT, std::vector<float>>) {
496
                return joinVector(vec[local_row]);
6✔
497
            } else {
498
                return {"?"};
×
499
            }
500
        });
118✔
501
    } catch (...) {
×
502
        return {"Error"};
×
503
    }
×
504
}
505

506
template<typename T>
507
QString PaginatedTableModel::joinVector(std::vector<T> const & values) {
6✔
508
    if (values.empty()) return QString();
6✔
509
    QString out;
6✔
510
    out.reserve(static_cast<int>(values.size() * 4));
6✔
511
    for (size_t i = 0; i < values.size(); ++i) {
21✔
512
        if constexpr (std::is_same_v<T, double>) {
513
            out += QString::number(values[i], 'f', 3);
6✔
514
        } else if constexpr (std::is_same_v<T, float>) {
515
            out += QString::number(static_cast<double>(values[i]), 'f', 3);
9✔
516
        } else {
517
            out += QString::number(values[i]);
×
518
        }
519
        if (i + 1 < values.size()) out += ",";
15✔
520
    }
521
    return out;
6✔
522
}
6✔
523

524
// Explicit instantiations
525
template QString PaginatedTableModel::joinVector<double>(std::vector<double> const &);
526
template QString PaginatedTableModel::joinVector<int>(std::vector<int> const &);
527
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