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

paulmthompson / WhiskerToolbox / 18685379784

21 Oct 2025 01:25PM UTC coverage: 72.522% (+0.1%) from 72.391%
18685379784

push

github

paulmthompson
fix failing tests

18 of 40 new or added lines in 1 file covered. (45.0%)

1765 existing lines in 32 files now uncovered.

53998 of 74457 relevant lines covered (72.52%)

46177.73 hits per line

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

69.89
/src/WhiskerToolbox/Feature_Tree_Widget/Feature_Tree_Widget.cpp
1
#include "Feature_Tree_Widget.hpp"
2
#include "ui_Feature_Tree_Widget.h"
3

4
#include "../DataManager/utils/color.hpp"
5
#include "DataManager.hpp"
6

7
#include <QCheckBox>
8
#include <QHeaderView>
9
#include <QTreeWidgetItem>
10
#include <iostream>
11
#include <regex>
12

13
Feature_Tree_Widget::Feature_Tree_Widget(QWidget * parent)
30✔
14
    : QWidget(parent),
15
      ui(new Ui::Feature_Tree_Widget) {
90✔
16

17
    ui->setupUi(this);
30✔
18

19
    // Configure tree widget
20
    ui->treeWidget->setColumnCount(3);
30✔
21
    ui->treeWidget->setHeaderLabels(QStringList() << "Feature" << "Enabled" << "Color");
30✔
22
    
23
    // Set column resize modes: Feature column stretches, others are fixed
24
    ui->treeWidget->header()->setSectionResizeMode(0, QHeaderView::Stretch);
30✔
25
    ui->treeWidget->header()->setSectionResizeMode(1, QHeaderView::Fixed);
30✔
26
    ui->treeWidget->header()->setSectionResizeMode(2, QHeaderView::Fixed);
30✔
27
    
28
    // Set fixed widths for checkbox and color columns
29
    ui->treeWidget->setColumnWidth(1, 60);  // "Enabled" checkbox column
30✔
30
    ui->treeWidget->setColumnWidth(2, 50);  // "Color" column
30✔
31
    
32
    ui->treeWidget->setSelectionMode(QAbstractItemView::SingleSelection);
30✔
33
    ui->treeWidget->setSortingEnabled(true);
30✔
34

35
    // Connect signals
36
    connect(ui->treeWidget, &QTreeWidget::itemClicked, this, &Feature_Tree_Widget::_itemSelected);
30✔
37
    connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &Feature_Tree_Widget::_itemChanged);
30✔
38
    //connect(ui->refreshButton, &QPushButton::clicked, this, &Feature_Tree_Widget::_refreshFeatures);
39
}
30✔
40

41
Feature_Tree_Widget::~Feature_Tree_Widget() {
60✔
42
    delete ui;
30✔
43
}
60✔
44

45
QTreeWidget * Feature_Tree_Widget::treeWidget() const { return ui->treeWidget; }
41✔
46

47
void Feature_Tree_Widget::setDataManager(std::shared_ptr<DataManager> data_manager) {
30✔
48
    _data_manager = std::move(data_manager);
30✔
49

50
    // Register observer for data manager updates
51
    _data_manager->addObserver([this]() {
30✔
52
        _refreshFeatures();
22✔
53
    });
22✔
54

55
    // Initial population
56
    _refreshFeatures();
30✔
57
}
30✔
58

59
void Feature_Tree_Widget::setGroupingPattern(std::string const & pattern) {
10✔
60
    _grouping_pattern = pattern;
10✔
61
    _refreshFeatures();
10✔
62
}
10✔
63

64
void Feature_Tree_Widget::setTypeFilters(std::vector<DM_DataType> types) {
20✔
65
    _type_filters = std::move(types);
20✔
66
    _refreshFeatures();
20✔
67
}
20✔
68

UNCOV
69
std::vector<std::string> Feature_Tree_Widget::getSelectedFeatures() const {
×
70
    QTreeWidgetItem * item = ui->treeWidget->currentItem();
×
71
    if (!item) {
×
UNCOV
72
        return {};
×
73
    }
74

75
    // Get feature key
UNCOV
76
    std::string const key = item->text(0).toStdString();
×
77

78
    // Check if it's a group
UNCOV
79
    if (_features.find(key) != _features.end() && _features.at(key).isGroup) {
×
UNCOV
80
        return _features.at(key).children;
×
81
    } else {
UNCOV
82
        return {key};
×
83
    }
UNCOV
84
}
×
85

86
void Feature_Tree_Widget::refreshTree() {
28✔
87
    _refreshFeatures();
28✔
88
}
28✔
89

90
void Feature_Tree_Widget::_itemSelected(QTreeWidgetItem * item, int column) {
×
91

92
    static_cast<void>(column);
93

94
    if (_is_rebuilding) return;// Suppress selections during rebuild
×
95

UNCOV
96
    if (!item) return;
×
97

98
    std::string const key = item->text(0).toStdString();
×
UNCOV
99
    std::vector<std::string> selectedFeatures;
×
100

101
    // Check if it's a group
102
    if (_features.find(key) != _features.end()) {
×
103
        if (_features[key].isGroup || _features[key].isDataTypeGroup) {
×
UNCOV
104
            selectedFeatures = _features[key].children;
×
105
        } else {
UNCOV
106
            selectedFeatures = {key};
×
UNCOV
107
            emit featureSelected(key);// Emit single feature selection
×
108
        }
109
    }
110

UNCOV
111
    emit featuresSelected(selectedFeatures);
×
UNCOV
112
}
×
113

114
void Feature_Tree_Widget::_itemChanged(QTreeWidgetItem * item, int column) {
2,428✔
115
    if (_is_rebuilding) return;      // Suppress changes during rebuild
2,428✔
116
    if (!item || column != 1) return;// Only process checkbox column
7✔
117

118
    std::string const key = item->text(0).toStdString();
7✔
119
    bool const enabled{item->checkState(1) == Qt::Checked};
7✔
120

121
    // Update the feature state
122
    if (_features.find(key) != _features.end()) {
7✔
123
        _features[key].enabled = enabled;
7✔
124

125
        std::vector<std::string> affectedFeatures;
7✔
126

127
        // Handle group toggling
128
        if (_features[key].isGroup || _features[key].isDataTypeGroup) {
7✔
129
            // Only propagate to children when parent is explicitly Checked/Unchecked.
130
            // If parent is PartiallyChecked (typically set programmatically from child changes),
131
            // do not overwrite child states and do not emit group signals.
132
            Qt::CheckState const parentState = item->checkState(column);
4✔
133
            if (parentState == Qt::Checked || parentState == Qt::Unchecked) {
4✔
134
                // Block signals while updating children to avoid per-child emissions
135
                if (ui && ui->treeWidget) ui->treeWidget->blockSignals(true);
2✔
136
                _updateChildrenState(item, column);
2✔
137
                if (ui && ui->treeWidget) ui->treeWidget->blockSignals(false);
2✔
138

139
                // Add all children to affected features
140
                affectedFeatures = _features[key].children;
2✔
141

142
                // Emit signals for multiple features only once per group toggle
143
                if (parentState == Qt::Checked) {
2✔
144
                    emit addFeatures(affectedFeatures);
2✔
145
                } else {
UNCOV
146
                    emit removeFeatures(affectedFeatures);
×
147
                }
148
            }
149
        } else {
150
            affectedFeatures = {key};
9✔
151

152
            // Update parent state if needed
153
            _updateParentState(item, column);
3✔
154

155
            // Emit single feature signals
156
            if (enabled) {
3✔
157
                emit addFeature(key);
3✔
158
            } else {
UNCOV
159
                emit removeFeature(key);
×
160
            }
161
        }
162
    }
7✔
163
}
10✔
164

165
void Feature_Tree_Widget::_refreshFeatures() {
110✔
166
    // Save current state before rebuilding
167
    _is_rebuilding = true;// Guard emissions
110✔
168
    _saveCurrentState();
110✔
169

170
    // Clear existing data
171
    ui->treeWidget->clear();
110✔
172
    _feature_items.clear();
110✔
173
    _group_items.clear();
110✔
174
    _datatype_items.clear();
110✔
175
    _features.clear();
110✔
176

177
    // Populate tree
178
    _populateTree();
110✔
179

180
    // Restore state after rebuilding
181
    _restoreState();
110✔
182
    _is_rebuilding = false;// End guard
110✔
183
}
110✔
184

185
void Feature_Tree_Widget::_populateTree() {
110✔
186

187
    if (!_data_manager) {
110✔
188
        return;
20✔
189
    }
190

191
    // Get all keys
192
    auto allKeys = _data_manager->getAllKeys();
90✔
193

194
    if (_organize_by_datatype) {
90✔
195
        _populateTreeByDataType(allKeys);
90✔
196
    } else {
UNCOV
197
        _populateTreeFlat(allKeys);
×
198
    }
199
}
90✔
200

201
void Feature_Tree_Widget::_populateTreeByDataType(std::vector<std::string> const & allKeys) {
90✔
202

203
    if (!_data_manager) {
90✔
UNCOV
204
        return;
×
205
    }
206

207
    // Organize by data type first
208
    std::unordered_map<DM_DataType, std::vector<std::string>> dataTypeGroups;
90✔
209

210
    for (auto const & key: allKeys) {
578✔
211
        auto const type = _data_manager->getType(key);
488✔
212
        if (!_type_filters.empty() && !_hasTypeFilter(type)) {
488✔
213
            continue;
54✔
214
        }
215
        dataTypeGroups[type].push_back(key);
434✔
216
    }
217

218
    // Create data type groups and organize within each
219
    for (auto const & [dataType, keys]: dataTypeGroups) {
365✔
220
        if (keys.empty()) continue;
275✔
221

222
        // Create data type group item
223
        QTreeWidgetItem * dataTypeItem = _getOrCreateDataTypeItem(dataType);
275✔
224

225
        // Within each data type, group by underscore pattern
226
        std::unordered_map<std::string, std::vector<std::string>> nameGroups;
275✔
227

228
        for (auto const & key: keys) {
709✔
229
            std::string const groupName = _extractGroupName(key);
434✔
230
            if (!groupName.empty() && groupName != key) {
434✔
231
                nameGroups[groupName].push_back(key);
307✔
232
            }
233
        }
434✔
234

235
        // Add grouped items
236
        for (auto const & [groupName, members]: nameGroups) {
431✔
237
            if (members.size() > 1) {
156✔
238
                // Create a name group under the data type
239
                TreeFeature groupFeature;
98✔
240
                groupFeature.key = groupName;
98✔
241
                groupFeature.type = "Group";
98✔
242
                groupFeature.isGroup = true;
98✔
243
                groupFeature.isDataTypeGroup = false;
98✔
244
                groupFeature.children = members;
98✔
245
                groupFeature.dataType = dataType;
98✔
246
                _features[groupName] = groupFeature;
98✔
247

248
                auto * groupItem = new QTreeWidgetItem(dataTypeItem);
98✔
249
                groupItem->setText(0, QString::fromStdString(groupName));
98✔
250
                groupItem->setFlags(groupItem->flags() | Qt::ItemIsUserCheckable);
98✔
251
                setup_checkbox_column(groupItem, 1, false);
98✔
252
                _group_items[groupName] = groupItem;
98✔
253

254
                // Add children
255
                for (auto const & member: members) {
347✔
256
                    TreeFeature childFeature;
249✔
257
                    childFeature.key = member;
249✔
258
                    childFeature.type = convert_data_type_to_string(dataType);
249✔
259
                    childFeature.timeFrame = _data_manager->getTimeKey(member).str();
249✔
260
                    childFeature.isGroup = false;
249✔
261
                    childFeature.isDataTypeGroup = false;
249✔
262
                    childFeature.dataType = dataType;
249✔
263
                    _features[member] = childFeature;
249✔
264

265
                    auto * childItem = new QTreeWidgetItem(groupItem);
249✔
266
                    childItem->setText(0, QString::fromStdString(member));
249✔
267
                    childItem->setFlags(childItem->flags() | Qt::ItemIsUserCheckable);
249✔
268
                    setup_checkbox_column(childItem, 1, false);
249✔
269
                    _feature_items[member] = childItem;
249✔
270
                }
249✔
271
            }
98✔
272
        }
273

274
        // Add standalone items
275
        for (auto const & key: keys) {
709✔
276
            bool inGroup = false;
434✔
277
            for (auto const & [groupName, members]: nameGroups) {
510✔
278
                if (members.size() > 1 && std::find(members.begin(), members.end(), key) != members.end()) {
325✔
279
                    inGroup = true;
249✔
280
                    break;
249✔
281
                }
282
            }
283

284
            if (!inGroup && _features.find(key) == _features.end()) {
434✔
285
                TreeFeature feature;
185✔
286
                feature.key = key;
185✔
287
                feature.type = convert_data_type_to_string(dataType);
185✔
288
                feature.timeFrame = _data_manager->getTimeKey(key).str();
185✔
289
                feature.isGroup = false;
185✔
290
                feature.isDataTypeGroup = false;
185✔
291
                feature.dataType = dataType;
185✔
292
                _features[key] = feature;
185✔
293

294
                auto * item = new QTreeWidgetItem(dataTypeItem);
185✔
295
                item->setText(0, QString::fromStdString(key));
185✔
296
                item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
185✔
297
                setup_checkbox_column(item, 1, false);
185✔
298
                _feature_items[key] = item;
185✔
299
            }
185✔
300
        }
301
    }
275✔
302

303
    ui->treeWidget->expandAll();
90✔
304
}
90✔
305

UNCOV
306
void Feature_Tree_Widget::_populateTreeFlat(std::vector<std::string> const & allKeys) {
×
307

UNCOV
308
    if (!_data_manager) {
×
309
        return;
×
310
    }
311

312
    // First pass: identify groups
313
    std::unordered_map<std::string, std::vector<std::string>> groups;
×
314

UNCOV
315
    for (auto const & key: allKeys) {
×
316
        // Skip if type doesn't match filter
317
        auto const type = _data_manager->getType(key);
×
UNCOV
318
        if (!_type_filters.empty() && !_hasTypeFilter(type)) {
×
319
            continue;
×
320
        }
321

322
        // Try to extract group name
323
        std::string const groupName = _extractGroupName(key);
×
324

UNCOV
325
        if (!groupName.empty() && groupName != key) {
×
326
            // This is part of a group
327
            groups[groupName].push_back(key);
×
328
        }
329
    }
×
330

331
    // Second pass: add groups and standalone items
332
    for (auto const & [groupName, members]: groups) {
×
333
        if (members.size() > 1) {
×
334
            // Create group in features
UNCOV
335
            TreeFeature groupFeature;
×
UNCOV
336
            groupFeature.key = groupName;
×
337
            groupFeature.type = "Group";
×
UNCOV
338
            groupFeature.isGroup = true;
×
UNCOV
339
            groupFeature.children = members;
×
340
            _features[groupName] = groupFeature;
×
341

342
            // Create tree item
343
            _addFeatureToTree(groupName, true);
×
344

345
            // Add children to the group
346
            for (auto const & member: members) {
×
347
                // Create feature
UNCOV
348
                TreeFeature childFeature;
×
UNCOV
349
                childFeature.key = member;
×
350
                childFeature.type = convert_data_type_to_string(_data_manager->getType(member));
×
351
                childFeature.timeFrame = _data_manager->getTimeKey(member).str();
×
352
                childFeature.isGroup = false;
×
353
                _features[member] = childFeature;
×
354

355
                // Add as child to tree
356
                QTreeWidgetItem * groupItem = _group_items[groupName];
×
UNCOV
357
                auto * childItem = new QTreeWidgetItem(groupItem);
×
358
                childItem->setText(0, QString::fromStdString(member));
×
359
                childItem->setFlags(childItem->flags() | Qt::ItemIsUserCheckable);
×
360

UNCOV
361
                setup_checkbox_column(childItem, 1, false);
×
362

UNCOV
363
                _feature_items[member] = childItem;
×
364
            }
×
UNCOV
365
        }
×
366
    }
367

368
    // Add standalone items (not in any group)
UNCOV
369
    for (auto const & key: allKeys) {
×
370
        // Skip if type doesn't match filter
UNCOV
371
        auto const type = _data_manager->getType(key);
×
372
        if (!_type_filters.empty() && !_hasTypeFilter(type)) {
×
373
            continue;
×
374
        }
375

376
        // Skip if already added as part of a group
UNCOV
377
        bool inGroup = false;
×
UNCOV
378
        for (auto const & [groupName, members]: groups) {
×
UNCOV
379
            if (members.size() > 1 && std::find(members.begin(), members.end(), key) != members.end()) {
×
380
                inGroup = true;
×
UNCOV
381
                break;
×
382
            }
383
        }
384

385
        if (!inGroup && _features.find(key) == _features.end()) {
×
386
            // Create feature
387
            TreeFeature feature;
×
UNCOV
388
            feature.key = key;
×
UNCOV
389
            feature.type = convert_data_type_to_string(type);
×
390
            feature.timeFrame = _data_manager->getTimeKey(key).str();
×
391
            feature.isGroup = false;
×
UNCOV
392
            _features[key] = feature;
×
393

394
            // Add to tree
395
            _addFeatureToTree(key);
×
396
        }
×
397
    }
398

399
    // Expand all items for better visibility
UNCOV
400
    ui->treeWidget->expandAll();
×
UNCOV
401
}
×
402

403
std::string Feature_Tree_Widget::_extractGroupName(std::string const & key) {
434✔
404
    std::regex const pattern{_grouping_pattern};
434✔
405
    std::smatch matches{};
434✔
406

407
    if (std::regex_search(key, matches, pattern) && matches.size() > 1) {
434✔
408
        return matches[1].str();
307✔
409
    }
410

411
    return key;// Return the key itself if no match
127✔
412
}
434✔
413

414
std::vector<std::string> get_child_features(QTreeWidgetItem * item) {
×
415
    std::vector<std::string> children;
×
UNCOV
416
    if (!item) return children;
×
417

418
    for (int i = 0; i < item->childCount(); i++) {
×
419
        QTreeWidgetItem * child = item->child(i);
×
UNCOV
420
        children.push_back(child->text(0).toStdString());
×
421
    }
422

423
    return children;
×
UNCOV
424
}
×
425

426
void Feature_Tree_Widget::_addFeatureToTree(std::string const & key, bool isGroup, bool isDataTypeGroup) {
×
427
    if (_features.find(key) == _features.end()) {
×
428
        return;
×
429
    }
430

431
    auto * item = new QTreeWidgetItem(ui->treeWidget);
×
UNCOV
432
    item->setText(0, QString::fromStdString(key));
×
433
    item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
×
434

435
    setup_checkbox_column(item, 1, false);
×
436

UNCOV
437
    if (isDataTypeGroup) {
×
438
        _datatype_items[key] = item;
×
UNCOV
439
    } else if (isGroup) {
×
UNCOV
440
        _group_items[key] = item;
×
441
    } else {
442
        _feature_items[key] = item;
×
443
    }
444
}
445

446
void Feature_Tree_Widget::_setupTreeItem(QTreeWidgetItem * item, TreeFeature const & feature) {
×
447
    item->setText(0, QString::fromStdString(feature.key));
×
448

UNCOV
449
    setup_checkbox_column(item, 1, feature.enabled);
×
UNCOV
450
}
×
451

452
void setup_checkbox_column(QTreeWidgetItem * item, int column, bool checked) {
807✔
453
    item->setCheckState(column, checked ? Qt::Checked : Qt::Unchecked);
807✔
454
}
807✔
455

456
bool Feature_Tree_Widget::_hasTypeFilter(DM_DataType const & type) {
225✔
457
    return std::find(_type_filters.begin(), _type_filters.end(), type) != _type_filters.end();
225✔
458
}
459

460
void Feature_Tree_Widget::_updateChildrenState(QTreeWidgetItem * parent, int column) {
2✔
461
    if (!parent) return;
2✔
462

463
    Qt::CheckState const parentState = parent->checkState(column);
2✔
464

465
    // Update all children with parent's state
466
    for (int i = 0; i < parent->childCount(); i++) {
9✔
467
        QTreeWidgetItem * child = parent->child(i);
7✔
468
        child->setCheckState(column, parentState);
7✔
469

470
        // Update feature state
471
        std::string const childKey = child->text(0).toStdString();
7✔
472
        if (_features.find(childKey) != _features.end()) {
7✔
473
            _features[childKey].enabled = (parentState == Qt::Checked);
7✔
474
        }
475
    }
7✔
476
}
477

478
void Feature_Tree_Widget::_updateParentState(QTreeWidgetItem * child, int column) {
3✔
479
    QTreeWidgetItem * parent = child->parent();
3✔
480
    if (!parent) return;
3✔
481

482
    bool allChecked = true;
3✔
483
    bool allUnchecked = true;
3✔
484

485
    // Check all siblings
486
    for (int i = 0; i < parent->childCount(); i++) {
9✔
487
        QTreeWidgetItem * sibling = parent->child(i);
6✔
488
        if (sibling->checkState(column) == Qt::Checked) {
6✔
489
            allUnchecked = false;
3✔
490
        } else {
491
            allChecked = false;
3✔
492
        }
493
    }
494

495
    // Set parent state based on children
496
    if (allChecked) {
3✔
UNCOV
497
        parent->setCheckState(column, Qt::Checked);
×
498
    } else if (allUnchecked) {
3✔
UNCOV
499
        parent->setCheckState(column, Qt::Unchecked);
×
500
    } else {
501
        parent->setCheckState(column, Qt::PartiallyChecked);
3✔
502
    }
503

504
    // Update feature state
505
    std::string const parentKey = parent->text(0).toStdString();
3✔
506
    if (_features.find(parentKey) != _features.end()) {
3✔
507
        _features[parentKey].enabled = (parent->checkState(column) == Qt::Checked);
3✔
508
    }
509
}
3✔
510

511
QTreeWidgetItem * Feature_Tree_Widget::_getOrCreateDataTypeItem(DM_DataType dataType) {
275✔
512
    std::string const dataTypeName = _getDataTypeGroupName(dataType);
275✔
513

514
    if (_datatype_items.find(dataTypeName) != _datatype_items.end()) {
275✔
UNCOV
515
        return _datatype_items[dataTypeName];
×
516
    }
517

518
    // Create new data type group
519
    TreeFeature dataTypeFeature;
275✔
520
    dataTypeFeature.key = dataTypeName;
275✔
521
    dataTypeFeature.type = "Data Type";
275✔
522
    dataTypeFeature.isGroup = true;
275✔
523
    dataTypeFeature.isDataTypeGroup = true;
275✔
524
    dataTypeFeature.dataType = dataType;
275✔
525
    _features[dataTypeName] = dataTypeFeature;
275✔
526

527
    auto * item = new QTreeWidgetItem(ui->treeWidget);
275✔
528
    item->setText(0, QString::fromStdString(dataTypeName));
275✔
529
    item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
275✔
530
    setup_checkbox_column(item, 1, false);
275✔
531

532
    _datatype_items[dataTypeName] = item;
275✔
533
    return item;
275✔
534
}
275✔
535

536
std::string Feature_Tree_Widget::_getDataTypeGroupName(DM_DataType dataType) {
275✔
537
    return convert_data_type_to_string(dataType);
275✔
538
}
539

540
std::string Feature_Tree_Widget::getSelectedFeature() const {
2✔
541
    QTreeWidgetItem * item = ui->treeWidget->currentItem();
2✔
542
    if (!item) {
2✔
UNCOV
543
        return "";
×
544
    }
545
    return item->text(0).toStdString();
4✔
546
}
547

548
void Feature_Tree_Widget::_saveCurrentState() {
110✔
549
    // Clear previous state
550
    _enabled_features.clear();
110✔
551
    _expanded_groups.clear();
110✔
552
    _selected_feature_for_restoration.clear();
110✔
553

554
    // Save currently selected feature
555
    QTreeWidgetItem * currentItem = ui->treeWidget->currentItem();
110✔
556
    if (currentItem) {
110✔
557
        _selected_feature_for_restoration = currentItem->text(0).toStdString();
1✔
558
    }
559

560
    // Helper function to recursively save state for all items
561
    std::function<void(QTreeWidgetItem *)> saveItemState = [&](QTreeWidgetItem * item) {
220✔
562
        if (!item) return;
558✔
563

564
        std::string const itemKey = item->text(0).toStdString();
558✔
565

566
        // Save enabled state (checkbox in column 1)
567
        if (item->checkState(1) == Qt::Checked) {
558✔
568
            _enabled_features.insert(itemKey);
1✔
569
        }
570

571
        // Save expanded state
572
        if (item->isExpanded()) {
558✔
573
            _expanded_groups.insert(itemKey);
5✔
574
        }
575

576
        // Recursively save child states
577
        for (int i = 0; i < item->childCount(); ++i) {
919✔
578
            saveItemState(item->child(i));
361✔
579
        }
580
    };
668✔
581

582
    // Save state for all top-level items
583
    for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
307✔
584
        saveItemState(ui->treeWidget->topLevelItem(i));
197✔
585
    }
586
}
220✔
587

588
void Feature_Tree_Widget::_restoreState() {
110✔
589
    // Block signals during state restoration to avoid triggering itemChanged
590
    ui->treeWidget->blockSignals(true);
110✔
591

592
    // Helper function to recursively restore state for all items
593
    std::function<void(QTreeWidgetItem *)> restoreItemState = [&](QTreeWidgetItem * item) {
220✔
594
        if (!item) return;
807✔
595

596
        std::string const itemKey = item->text(0).toStdString();
807✔
597

598
        // Restore enabled state (checkbox in column 1)
599
        bool const shouldBeEnabled = _enabled_features.find(itemKey) != _enabled_features.end();
807✔
600
        item->setCheckState(1, shouldBeEnabled ? Qt::Checked : Qt::Unchecked);
807✔
601

602
        // Update the feature state in our internal tracking
603
        if (_features.find(itemKey) != _features.end()) {
807✔
604
            _features[itemKey].enabled = shouldBeEnabled;
807✔
605
        }
606

607
        // Restore expanded state
608
        bool const shouldBeExpanded = _expanded_groups.find(itemKey) != _expanded_groups.end();
807✔
609
        item->setExpanded(shouldBeExpanded);
807✔
610

611
        // Restore selection state
612
        if (itemKey == _selected_feature_for_restoration) {
807✔
613
            ui->treeWidget->setCurrentItem(item);
1✔
614
        }
615

616
        // Recursively restore child states
617
        for (int i = 0; i < item->childCount(); ++i) {
1,339✔
618
            restoreItemState(item->child(i));
532✔
619
        }
620
    };
917✔
621

622
    // Restore state for all top-level items
623
    for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i) {
385✔
624
        restoreItemState(ui->treeWidget->topLevelItem(i));
275✔
625
    }
626

627
    // Unblock signals after restoration is complete
628
    ui->treeWidget->blockSignals(false);
110✔
629
}
220✔
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