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

paulmthompson / WhiskerToolbox / 18246927847

04 Oct 2025 04:44PM UTC coverage: 71.826% (+0.6%) from 71.188%
18246927847

push

github

paulmthompson
refactor out media producer consumer pipeline

0 of 120 new or added lines in 2 files covered. (0.0%)

646 existing lines in 14 files now uncovered.

48895 of 68074 relevant lines covered (71.83%)

1193.51 hits per line

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

69.29
/src/WhiskerToolbox/GroupManagementWidget/GroupManager.cpp
1
#include "GroupManager.hpp"
2

3
#include "Entity/EntityGroupManager.hpp"
4
#include "DataManager/DataManager.hpp"
5
#include "DataManager/Points/Point_Data.hpp"
6
#include "DataManager/Lines/Line_Data.hpp"
7

8
#include <QDebug>
9

10
QVector<QColor> const GroupManager::DEFAULT_COLORS = {
11
        QColor(31, 119, 180), // Blue
12
        QColor(255, 127, 14), // Orange
13
        QColor(44, 160, 44),  // Green
14
        QColor(214, 39, 40),  // Red
15
        QColor(148, 103, 189),// Purple
16
        QColor(140, 86, 75),  // Brown
17
        QColor(227, 119, 194),// Pink
18
        QColor(127, 127, 127),// Gray
19
        QColor(188, 189, 34), // Olive
20
        QColor(23, 190, 207)  // Cyan
21
};
22

23
GroupManager::GroupManager(EntityGroupManager * entity_group_manager, std::shared_ptr<DataManager> data_manager, QObject * parent)
45✔
24
    : QObject(parent),
25
      m_entity_group_manager(entity_group_manager),
45✔
26
      m_data_manager(std::move(data_manager)),
45✔
27
      m_next_group_id(1) {
90✔
28
    // Assert that we have valid managers
29
    Q_ASSERT(m_entity_group_manager != nullptr);
45✔
30
    Q_ASSERT(m_data_manager != nullptr);
45✔
31
}
45✔
32

33
int GroupManager::createGroup(QString const & name) {
40✔
34
    QColor const color = getNextDefaultColor();
40✔
35
    return createGroup(name, color);
80✔
36
}
37

38
int GroupManager::createGroup(QString const & name, QColor const & color) {
40✔
39

40
    GroupId const entity_group_id = m_entity_group_manager->createGroup(name.toStdString());
120✔
41

42
    auto const group_id = static_cast<int>(entity_group_id);
40✔
43
    m_group_colors[group_id] = color;
40✔
44
    m_group_visibility[group_id] = true; // Groups are visible by default
40✔
45

46
    qDebug() << "GroupManager: Created group" << group_id << "with name" << name;
40✔
47

48
    emit groupCreated(group_id);
40✔
49
    return group_id;
40✔
50
}
51

52
bool GroupManager::removeGroup(int group_id) {
3✔
53
    auto entity_group_id = static_cast<GroupId>(group_id);
3✔
54

55
    if (!m_entity_group_manager->deleteGroup(entity_group_id)) {
3✔
56
        return false;
1✔
57
    }
58

59
    m_group_colors.remove(group_id);
2✔
60
    m_group_visibility.remove(group_id);
2✔
61

62
    qDebug() << "GroupManager: Removed group" << group_id;
2✔
63

64
    emit groupRemoved(group_id);
2✔
65
    return true;
2✔
66
}
67

68
std::optional<GroupManager::Group> GroupManager::getGroup(int group_id) const {
28✔
69
    auto entity_group_id = static_cast<GroupId>(group_id);
28✔
70

71
    auto descriptor = m_entity_group_manager->getGroupDescriptor(entity_group_id);
28✔
72
    if (!descriptor.has_value()) {
28✔
UNCOV
73
        return std::nullopt;
×
74
    }
75

76
    Group group(group_id,
28✔
77
                QString::fromStdString(descriptor->name),
56✔
78
                m_group_colors.value(group_id, QColor(128, 128, 128)),
56✔
79
                m_group_visibility.value(group_id, true));
112✔
80

81
    return group;
28✔
82
}
28✔
83

84
bool GroupManager::setGroupName(int group_id, QString const & name) {
10✔
85
    auto entity_group_id = static_cast<GroupId>(group_id);
10✔
86

87
    // Get current descriptor to preserve description
88
    auto descriptor = m_entity_group_manager->getGroupDescriptor(entity_group_id);
10✔
89
    if (!descriptor.has_value()) {
10✔
UNCOV
90
        return false;
×
91
    }
92

93
    // Avoid redundant updates/signals if the name is unchanged
94
    if (QString::fromStdString(descriptor->name) == name) {
10✔
95
        return true;
6✔
96
    }
97

98
    if (!m_entity_group_manager->updateGroup(entity_group_id, name.toStdString(), descriptor->description)) {
4✔
UNCOV
99
        return false;
×
100
    }
101

102
    qDebug() << "GroupManager: Updated group" << group_id << "name to" << name;
4✔
103

104
    emit groupModified(group_id);
4✔
105
    return true;
4✔
106
}
10✔
107

108
bool GroupManager::setGroupColor(int group_id, QColor const & color) {
2✔
109
    auto entity_group_id = static_cast<GroupId>(group_id);
2✔
110

111
    if (!m_entity_group_manager->hasGroup(entity_group_id)) {
2✔
UNCOV
112
        return false;
×
113
    }
114

115
    m_group_colors[group_id] = color;
2✔
116

117
    qDebug() << "GroupManager: Updated group" << group_id << "color";
2✔
118

119
    emit groupModified(group_id);
2✔
120
    return true;
2✔
121
}
122

123
bool GroupManager::setGroupVisibility(int group_id, bool visible) {
12✔
124
    auto entity_group_id = static_cast<GroupId>(group_id);
12✔
125

126
    if (!m_entity_group_manager->hasGroup(entity_group_id)) {
12✔
127
        return false;
1✔
128
    }
129

130
    m_group_visibility[group_id] = visible;
11✔
131

132
    qDebug() << "GroupManager: Updated group" << group_id << "visibility to" << visible;
11✔
133

134
    emit groupModified(group_id);
11✔
135
    return true;
11✔
136
}
137

138
QMap<int, GroupManager::Group> GroupManager::getGroups() const {
41✔
139
    QMap<int, Group> result;
41✔
140

141
    auto group_ids = m_entity_group_manager->getAllGroupIds();
41✔
142
    for (GroupId const entity_group_id: group_ids) {
55✔
143
        auto descriptor = m_entity_group_manager->getGroupDescriptor(entity_group_id);
14✔
144
        if (descriptor.has_value()) {
14✔
145
            auto group_id = static_cast<int>(entity_group_id);
14✔
146
            Group const group(group_id,
14✔
147
                              QString::fromStdString(descriptor->name),
28✔
148
                              m_group_colors.value(group_id, QColor(128, 128, 128)),
28✔
149
                              m_group_visibility.value(group_id, true));
56✔
150
            result[group_id] = group;
14✔
151
        }
14✔
152
    }
14✔
153

154
    return result;
41✔
155
}
41✔
156

157
// ===== EntityId-based API =====
158
bool GroupManager::assignEntitiesToGroup(int group_id, std::unordered_set<EntityId> const & entity_ids) {
19✔
159
    auto entity_group_id = static_cast<GroupId>(group_id);
19✔
160

161
    if (!m_entity_group_manager->hasGroup(entity_group_id)) {
19✔
162
        return false;
×
163
    }
164

165
    std::vector<EntityId> const entity_vector(entity_ids.begin(), entity_ids.end());
57✔
166

167
    std::size_t const added_count = m_entity_group_manager->addEntitiesToGroup(entity_group_id, entity_vector);
19✔
168

169
    qDebug() << "GroupManager: Assigned" << added_count << "entities to group" << group_id;
19✔
170
    if (added_count > 0) {
19✔
171
        emit groupModified(group_id);
17✔
172
    }
173

174
    return added_count > 0;
19✔
175
}
19✔
176

177
bool GroupManager::removeEntitiesFromGroup(int group_id, std::unordered_set<EntityId> const & entity_ids) {
6✔
178
    auto entity_group_id = static_cast<GroupId>(group_id);
6✔
179

180
    if (!m_entity_group_manager->hasGroup(entity_group_id)) {
6✔
UNCOV
181
        return false;
×
182
    }
183

184
    std::vector<EntityId> const entity_vector(entity_ids.begin(), entity_ids.end());
18✔
185

186
    std::size_t const removed_count = m_entity_group_manager->removeEntitiesFromGroup(entity_group_id, entity_vector);
6✔
187

188
    if (removed_count > 0) {
6✔
189
        qDebug() << "GroupManager: Removed" << removed_count << "entities from group" << group_id;
4✔
190
        emit groupModified(group_id);
4✔
191
    }
192

193
    return removed_count > 0;
6✔
194
}
6✔
195

196
void GroupManager::ungroupEntities(std::unordered_set<EntityId> const & entity_ids) {
1✔
197
    std::unordered_set<int> affected_groups;
1✔
198

199
    // For each entity, find which groups it belongs to and remove it
200
    for (EntityId const entity_id: entity_ids) {
2✔
201
        auto group_ids = m_entity_group_manager->getGroupsContainingEntity(entity_id);
1✔
202
        for (GroupId const entity_group_id: group_ids) {
3✔
203
            auto const group_id = static_cast<int>(entity_group_id);
2✔
204
            affected_groups.insert(group_id);
2✔
205

206
            std::vector<EntityId> const single_entity = {entity_id};
6✔
207
            m_entity_group_manager->removeEntitiesFromGroup(entity_group_id, single_entity);
2✔
208
        }
2✔
209
    }
1✔
210

211
    if (!affected_groups.empty()) {
1✔
212
        qDebug() << "GroupManager: Ungrouped" << entity_ids.size() << "entities from" << affected_groups.size() << "groups";
1✔
213
        for (int const gid: affected_groups) {
3✔
214
            emit groupModified(gid);
2✔
215
        }
216
    }
217
}
2✔
218

219
int GroupManager::getEntityGroup(EntityId id) const {
38✔
220
    auto group_ids = m_entity_group_manager->getGroupsContainingEntity(id);
38✔
221

222
    // Return the first group (assuming entities belong to at most one group for now)
223
    if (!group_ids.empty()) {
38✔
224
        return static_cast<int>(group_ids[0]);
13✔
225
    }
226

227
    return -1;// Not in any group
25✔
228
}
38✔
229

UNCOV
230
QColor GroupManager::getEntityColor(EntityId id, QColor const & default_color) const {
×
UNCOV
231
    int const group_id = getEntityGroup(id);
×
232
    if (group_id == -1) {
×
UNCOV
233
        return default_color;
×
234
    }
235

UNCOV
236
    return m_group_colors.value(group_id, default_color);
×
237
}
238

239
bool GroupManager::isEntityGroupVisible(EntityId id) const {
10✔
240
    int const group_id = getEntityGroup(id);
10✔
241
    if (group_id == -1) {
10✔
242
        return true; // Entities not in a group are always visible
1✔
243
    }
244

245
    return m_group_visibility.value(group_id, true);
9✔
246
}
247

248
int GroupManager::getGroupMemberCount(int group_id) const {
26✔
249
    auto entity_group_id = static_cast<GroupId>(group_id);
26✔
250
    return static_cast<int>(m_entity_group_manager->getGroupSize(entity_group_id));
26✔
251
}
252

253
void GroupManager::clearAllGroups() {
×
254
    qDebug() << "GroupManager: Clearing all groups";
×
255

256
    // Clear EntityGroupManager
257
    m_entity_group_manager->clear();
×
258

259
    // Clear our color and visibility mappings
260
    m_group_colors.clear();
×
261
    m_group_visibility.clear();
×
262

UNCOV
263
    m_next_group_id = 1;
×
264

265
    // Note: We don't emit specific signals here since everything is being cleared
UNCOV
266
}
×
267

268
QColor GroupManager::getNextDefaultColor() const {
40✔
269
    if (DEFAULT_COLORS.isEmpty()) {
40✔
270
        return {128, 128, 128};// Fallback gray
×
271
    }
272

273
    // Cycle through the default colors based on current group count
274
    auto group_ids = m_entity_group_manager->getAllGroupIds();
40✔
275
    auto color_index = static_cast<int>(group_ids.size()) % DEFAULT_COLORS.size();
40✔
276
    return DEFAULT_COLORS[color_index];
40✔
277
}
40✔
278

279
// ===== Common Group Operations for Context Menus =====
280

281
int GroupManager::createGroupWithEntities(std::unordered_set<EntityId> const & entity_ids) {
×
282
    if (entity_ids.empty()) {
×
UNCOV
283
        return -1;
×
284
    }
285

286
    QString group_name = QString("Group %1").arg(m_entity_group_manager->getAllGroupIds().size() + 1);
×
287
    int group_id = createGroup(group_name);
×
288
    
289
    if (group_id != -1) {
×
UNCOV
290
        assignEntitiesToGroup(group_id, entity_ids);
×
291
    }
292
    
UNCOV
293
    return group_id;
×
UNCOV
294
}
×
295

296
std::vector<std::pair<int, QString>> GroupManager::getGroupsForContextMenu() const {
×
UNCOV
297
    std::vector<std::pair<int, QString>> result;
×
298
    
UNCOV
299
    auto groups = getGroups();
×
300
    for (auto it = groups.begin(); it != groups.end(); ++it) {
×
UNCOV
301
        result.emplace_back(it.key(), it.value().name);
×
302
    }
303
    
UNCOV
304
    return result;
×
UNCOV
305
}
×
306

307
bool GroupManager::mergeGroups(int target_group_id, std::vector<int> const & source_group_ids) {
5✔
308
    // Validate target group exists
309
    auto target_entity_group_id = static_cast<GroupId>(target_group_id);
5✔
310
    if (!m_entity_group_manager->hasGroup(target_entity_group_id)) {
5✔
311
        qDebug() << "GroupManager: Target group" << target_group_id << "does not exist";
1✔
312
        return false;
1✔
313
    }
314

315
    // Validate source groups exist and are different from target
316
    for (int source_group_id : source_group_ids) {
7✔
317
        if (source_group_id == target_group_id) {
5✔
318
            qDebug() << "GroupManager: Cannot merge group into itself:" << source_group_id;
1✔
319
            return false;
1✔
320
        }
321
        
322
        auto source_entity_group_id = static_cast<GroupId>(source_group_id);
4✔
323
        if (!m_entity_group_manager->hasGroup(source_entity_group_id)) {
4✔
324
            qDebug() << "GroupManager: Source group" << source_group_id << "does not exist";
1✔
325
            return false;
1✔
326
        }
327
    }
328

329
    // Collect all entities from source groups
330
    std::unordered_set<EntityId> entities_to_merge;
2✔
331
    for (int source_group_id : source_group_ids) {
5✔
332
        auto source_entity_group_id = static_cast<GroupId>(source_group_id);
3✔
333
        auto entities_in_group = m_entity_group_manager->getEntitiesInGroup(source_entity_group_id);
3✔
334
        
335
        for (EntityId entity_id : entities_in_group) {
10✔
336
            entities_to_merge.insert(entity_id);
7✔
337
        }
338
    }
3✔
339

340
    // Move all entities to target group
341
    if (!entities_to_merge.empty()) {
2✔
342
        std::vector<EntityId> entities_vector(entities_to_merge.begin(), entities_to_merge.end());
6✔
343
        m_entity_group_manager->addEntitiesToGroup(target_entity_group_id, entities_vector);
2✔
344
    }
2✔
345

346
    // Remove source groups
347
    for (int source_group_id : source_group_ids) {
5✔
348
        auto source_entity_group_id = static_cast<GroupId>(source_group_id);
3✔
349
        
350
        // Remove all entities from source group first
351
        auto entities_in_group = m_entity_group_manager->getEntitiesInGroup(source_entity_group_id);
3✔
352
        if (!entities_in_group.empty()) {
3✔
353
            std::vector<EntityId> entities_vector(entities_in_group.begin(), entities_in_group.end());
9✔
354
            m_entity_group_manager->removeEntitiesFromGroup(source_entity_group_id, entities_vector);
3✔
355
        }
3✔
356
        
357
        // Delete the source group
358
        m_entity_group_manager->deleteGroup(source_entity_group_id);
3✔
359
        
360
        // Clean up our mappings
361
        m_group_colors.remove(source_group_id);
3✔
362
        m_group_visibility.remove(source_group_id);
3✔
363
        
364
        qDebug() << "GroupManager: Removed source group" << source_group_id;
3✔
365
        emit groupRemoved(source_group_id);
3✔
366
    }
3✔
367

368
    qDebug() << "GroupManager: Merged" << source_group_ids.size() << "groups into group" << target_group_id;
2✔
369
    emit groupModified(target_group_id);
2✔
370
    
371
    return true;
2✔
372
}
2✔
373

UNCOV
374
bool GroupManager::deleteGroupAndEntities(int group_id) {
×
UNCOV
375
    auto entity_group_id = static_cast<GroupId>(group_id);
×
376

UNCOV
377
    if (!m_entity_group_manager->hasGroup(entity_group_id)) {
×
UNCOV
378
        return false;
×
379
    }
380

381
    // Get all entities in the group
UNCOV
382
    auto entities = m_entity_group_manager->getEntitiesInGroup(entity_group_id);
×
UNCOV
383
    if (entities.empty()) {
×
384
        // No entities to delete, just remove the group
UNCOV
385
        return removeGroup(group_id);
×
386
    }
387

UNCOV
388
    qDebug() << "GroupManager: Deleting group" << group_id << "with" << entities.size() << "entities";
×
389

390
    // Remove entities from their respective data objects
UNCOV
391
    for (EntityId const entity_id : entities) {
×
UNCOV
392
        removeEntityFromDataObjects(entity_id);
×
393
    }
394

395
    // Remove the group (this will also remove all entity-group associations)
UNCOV
396
    bool const group_removed = removeGroup(group_id);
×
397

UNCOV
398
    if (group_removed) {
×
UNCOV
399
        qDebug() << "GroupManager: Successfully deleted group" << group_id << "and all its entities";
×
400
    }
401

UNCOV
402
    return group_removed;
×
UNCOV
403
}
×
404

UNCOV
405
void GroupManager::removeEntityFromDataObjects(EntityId entity_id) {
×
406
    // Get all data keys from the DataManager
UNCOV
407
    auto const data_keys = m_data_manager->getAllKeys();
×
408
    
UNCOV
409
    for (std::string const & key : data_keys) {
×
UNCOV
410
        auto data_variant = m_data_manager->getDataVariant(key);
×
UNCOV
411
        if (!data_variant.has_value()) {
×
UNCOV
412
            continue;
×
413
        }
414

415
        // Handle different data types
UNCOV
416
        std::visit([this, &key, entity_id](auto & data_ptr) {
×
417
            if constexpr (std::is_same_v<std::decay_t<decltype(data_ptr)>, std::shared_ptr<PointData>>) {
UNCOV
418
                if (data_ptr) {
×
UNCOV
419
                    removeEntityFromPointData(data_ptr.get(), entity_id);
×
420
                }
421
            } else if constexpr (std::is_same_v<std::decay_t<decltype(data_ptr)>, std::shared_ptr<LineData>>) {
UNCOV
422
                if (data_ptr) {
×
UNCOV
423
                    removeEntityFromLineData(data_ptr.get(), entity_id);
×
424
                }
425
            }
426
            // Note: DigitalEventSeries and DigitalIntervalSeries don't have entity lookup methods
427
            // so we skip them for now
UNCOV
428
        }, data_variant.value());
×
UNCOV
429
    }
×
UNCOV
430
}
×
431

UNCOV
432
void GroupManager::removeEntityFromPointData(PointData * point_data, EntityId entity_id) {
×
UNCOV
433
    if (!point_data) return;
×
434

435
    // Find the time and index for this entity
UNCOV
436
    auto time_and_index = point_data->getTimeAndIndexByEntityId(entity_id);
×
UNCOV
437
    if (!time_and_index.has_value()) {
×
UNCOV
438
        return;
×
439
    }
440

UNCOV
441
    auto const [time, index] = time_and_index.value();
×
442
    
443
    // Remove the point at the specific time and index
UNCOV
444
    point_data->clearAtTime(time, static_cast<size_t>(index), true);
×
445
}
446

UNCOV
447
void GroupManager::removeEntityFromLineData(LineData * line_data, EntityId entity_id) {
×
UNCOV
448
    if (!line_data) return;
×
449

450
    // Find the time and index for this entity
UNCOV
451
    auto time_and_index = line_data->getTimeAndIndexByEntityId(entity_id);
×
UNCOV
452
    if (!time_and_index.has_value()) {
×
UNCOV
453
        return;
×
454
    }
455

UNCOV
456
    auto const [time, index] = time_and_index.value();
×
457
    
458
    // Remove the line at the specific time and index
UNCOV
459
    line_data->clearAtTime(time, index, true);
×
460
}
461

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