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

paulmthompson / WhiskerToolbox / 18080728054

28 Sep 2025 06:50PM UTC coverage: 70.231% (-0.08%) from 70.31%
18080728054

push

github

paulmthompson
can delete from group manager

29 of 108 new or added lines in 7 files covered. (26.85%)

44242 of 62995 relevant lines covered (70.23%)

1123.28 hits per line

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

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

3
#include "DataManager/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)
34✔
24
    : QObject(parent),
25
      m_entity_group_manager(entity_group_manager),
34✔
26
      m_data_manager(std::move(data_manager)),
34✔
27
      m_next_group_id(1) {
68✔
28
    // Assert that we have valid managers
29
    Q_ASSERT(m_entity_group_manager != nullptr);
34✔
30
    Q_ASSERT(m_data_manager != nullptr);
34✔
31
}
34✔
32

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

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

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

42
    auto const group_id = static_cast<int>(entity_group_id);
19✔
43
    m_group_colors[group_id] = color;
19✔
44

45
    qDebug() << "GroupManager: Created group" << group_id << "with name" << name;
19✔
46

47
    emit groupCreated(group_id);
19✔
48
    return group_id;
19✔
49
}
50

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

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

58
    m_group_colors.remove(group_id);
2✔
59

60
    qDebug() << "GroupManager: Removed group" << group_id;
2✔
61

62
    emit groupRemoved(group_id);
2✔
63
    return true;
2✔
64
}
65

66
std::optional<GroupManager::Group> GroupManager::getGroup(int group_id) const {
15✔
67
    auto entity_group_id = static_cast<GroupId>(group_id);
15✔
68

69
    auto descriptor = m_entity_group_manager->getGroupDescriptor(entity_group_id);
15✔
70
    if (!descriptor.has_value()) {
15✔
71
        return std::nullopt;
×
72
    }
73

74
    Group group(group_id,
15✔
75
                QString::fromStdString(descriptor->name),
30✔
76
                m_group_colors.value(group_id, QColor(128, 128, 128)));
45✔
77

78
    return group;
15✔
79
}
15✔
80

81
bool GroupManager::setGroupName(int group_id, QString const & name) {
9✔
82
    auto entity_group_id = static_cast<GroupId>(group_id);
9✔
83

84
    // Get current descriptor to preserve description
85
    auto descriptor = m_entity_group_manager->getGroupDescriptor(entity_group_id);
9✔
86
    if (!descriptor.has_value()) {
9✔
87
        return false;
×
88
    }
89

90
    // Avoid redundant updates/signals if the name is unchanged
91
    if (QString::fromStdString(descriptor->name) == name) {
9✔
92
        return true;
5✔
93
    }
94

95
    if (!m_entity_group_manager->updateGroup(entity_group_id, name.toStdString(), descriptor->description)) {
4✔
96
        return false;
×
97
    }
98

99
    qDebug() << "GroupManager: Updated group" << group_id << "name to" << name;
4✔
100

101
    emit groupModified(group_id);
4✔
102
    return true;
4✔
103
}
9✔
104

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

108
    if (!m_entity_group_manager->hasGroup(entity_group_id)) {
2✔
109
        return false;
×
110
    }
111

112
    m_group_colors[group_id] = color;
2✔
113

114
    qDebug() << "GroupManager: Updated group" << group_id << "color";
2✔
115

116
    emit groupModified(group_id);
2✔
117
    return true;
2✔
118
}
119

120
QMap<int, GroupManager::Group> GroupManager::getGroups() const {
40✔
121
    QMap<int, Group> result;
40✔
122

123
    auto group_ids = m_entity_group_manager->getAllGroupIds();
40✔
124
    for (GroupId const entity_group_id: group_ids) {
54✔
125
        auto descriptor = m_entity_group_manager->getGroupDescriptor(entity_group_id);
14✔
126
        if (descriptor.has_value()) {
14✔
127
            auto group_id = static_cast<int>(entity_group_id);
14✔
128
            Group const group(group_id,
14✔
129
                              QString::fromStdString(descriptor->name),
28✔
130
                              m_group_colors.value(group_id, QColor(128, 128, 128)));
42✔
131
            result[group_id] = group;
14✔
132
        }
14✔
133
    }
14✔
134

135
    return result;
40✔
136
}
40✔
137

138
// ===== EntityId-based API =====
139
bool GroupManager::assignEntitiesToGroup(int group_id, std::unordered_set<EntityId> const & entity_ids) {
13✔
140
    auto entity_group_id = static_cast<GroupId>(group_id);
13✔
141

142
    if (!m_entity_group_manager->hasGroup(entity_group_id)) {
13✔
143
        return false;
×
144
    }
145

146
    std::vector<EntityId> const entity_vector(entity_ids.begin(), entity_ids.end());
39✔
147

148
    std::size_t const added_count = m_entity_group_manager->addEntitiesToGroup(entity_group_id, entity_vector);
13✔
149

150
    qDebug() << "GroupManager: Assigned" << added_count << "entities to group" << group_id;
13✔
151
    if (added_count > 0) {
13✔
152
        emit groupModified(group_id);
11✔
153
    }
154

155
    return added_count > 0;
13✔
156
}
13✔
157

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

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

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

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

169
    if (removed_count > 0) {
6✔
170
        qDebug() << "GroupManager: Removed" << removed_count << "entities from group" << group_id;
4✔
171
        emit groupModified(group_id);
4✔
172
    }
173

174
    return removed_count > 0;
6✔
175
}
6✔
176

177
void GroupManager::ungroupEntities(std::unordered_set<EntityId> const & entity_ids) {
1✔
178
    std::unordered_set<int> affected_groups;
1✔
179

180
    // For each entity, find which groups it belongs to and remove it
181
    for (EntityId const entity_id: entity_ids) {
2✔
182
        auto group_ids = m_entity_group_manager->getGroupsContainingEntity(entity_id);
1✔
183
        for (GroupId const entity_group_id: group_ids) {
3✔
184
            auto const group_id = static_cast<int>(entity_group_id);
2✔
185
            affected_groups.insert(group_id);
2✔
186

187
            std::vector<EntityId> const single_entity = {entity_id};
6✔
188
            m_entity_group_manager->removeEntitiesFromGroup(entity_group_id, single_entity);
2✔
189
        }
2✔
190
    }
1✔
191

192
    if (!affected_groups.empty()) {
1✔
193
        qDebug() << "GroupManager: Ungrouped" << entity_ids.size() << "entities from" << affected_groups.size() << "groups";
1✔
194
        for (int const gid: affected_groups) {
3✔
195
            emit groupModified(gid);
2✔
196
        }
197
    }
198
}
2✔
199

200
int GroupManager::getEntityGroup(EntityId id) const {
28✔
201
    auto group_ids = m_entity_group_manager->getGroupsContainingEntity(id);
28✔
202

203
    // Return the first group (assuming entities belong to at most one group for now)
204
    if (!group_ids.empty()) {
28✔
205
        return static_cast<int>(group_ids[0]);
4✔
206
    }
207

208
    return -1;// Not in any group
24✔
209
}
28✔
210

211
QColor GroupManager::getEntityColor(EntityId id, QColor const & default_color) const {
×
212
    int const group_id = getEntityGroup(id);
×
213
    if (group_id == -1) {
×
214
        return default_color;
×
215
    }
216

217
    return m_group_colors.value(group_id, default_color);
×
218
}
219

220
int GroupManager::getGroupMemberCount(int group_id) const {
18✔
221
    auto entity_group_id = static_cast<GroupId>(group_id);
18✔
222
    return static_cast<int>(m_entity_group_manager->getGroupSize(entity_group_id));
18✔
223
}
224

225
void GroupManager::clearAllGroups() {
×
226
    qDebug() << "GroupManager: Clearing all groups";
×
227

228
    // Clear EntityGroupManager
229
    m_entity_group_manager->clear();
×
230

231
    // Clear our color mappings
232
    m_group_colors.clear();
×
233

234
    m_next_group_id = 1;
×
235

236
    // Note: We don't emit specific signals here since everything is being cleared
237
}
×
238

239
QColor GroupManager::getNextDefaultColor() const {
19✔
240
    if (DEFAULT_COLORS.isEmpty()) {
19✔
241
        return {128, 128, 128};// Fallback gray
×
242
    }
243

244
    // Cycle through the default colors based on current group count
245
    auto group_ids = m_entity_group_manager->getAllGroupIds();
19✔
246
    auto color_index = static_cast<int>(group_ids.size()) % DEFAULT_COLORS.size();
19✔
247
    return DEFAULT_COLORS[color_index];
19✔
248
}
19✔
249

250
// ===== Common Group Operations for Context Menus =====
251

252
int GroupManager::createGroupWithEntities(std::unordered_set<EntityId> const & entity_ids) {
×
253
    if (entity_ids.empty()) {
×
254
        return -1;
×
255
    }
256

257
    QString group_name = QString("Group %1").arg(m_entity_group_manager->getAllGroupIds().size() + 1);
×
258
    int group_id = createGroup(group_name);
×
259
    
260
    if (group_id != -1) {
×
261
        assignEntitiesToGroup(group_id, entity_ids);
×
262
    }
263
    
264
    return group_id;
×
265
}
×
266

267
std::vector<std::pair<int, QString>> GroupManager::getGroupsForContextMenu() const {
×
268
    std::vector<std::pair<int, QString>> result;
×
269
    
270
    auto groups = getGroups();
×
271
    for (auto it = groups.begin(); it != groups.end(); ++it) {
×
272
        result.emplace_back(it.key(), it.value().name);
×
273
    }
274
    
275
    return result;
×
276
}
×
277

NEW
278
bool GroupManager::deleteGroupAndEntities(int group_id) {
×
NEW
279
    auto entity_group_id = static_cast<GroupId>(group_id);
×
280

NEW
281
    if (!m_entity_group_manager->hasGroup(entity_group_id)) {
×
NEW
282
        return false;
×
283
    }
284

285
    // Get all entities in the group
NEW
286
    auto entities = m_entity_group_manager->getEntitiesInGroup(entity_group_id);
×
NEW
287
    if (entities.empty()) {
×
288
        // No entities to delete, just remove the group
NEW
289
        return removeGroup(group_id);
×
290
    }
291

NEW
292
    qDebug() << "GroupManager: Deleting group" << group_id << "with" << entities.size() << "entities";
×
293

294
    // Remove entities from their respective data objects
NEW
295
    for (EntityId const entity_id : entities) {
×
NEW
296
        removeEntityFromDataObjects(entity_id);
×
297
    }
298

299
    // Remove the group (this will also remove all entity-group associations)
NEW
300
    bool const group_removed = removeGroup(group_id);
×
301

NEW
302
    if (group_removed) {
×
NEW
303
        qDebug() << "GroupManager: Successfully deleted group" << group_id << "and all its entities";
×
304
    }
305

NEW
306
    return group_removed;
×
NEW
307
}
×
308

NEW
309
void GroupManager::removeEntityFromDataObjects(EntityId entity_id) {
×
310
    // Get all data keys from the DataManager
NEW
311
    auto const data_keys = m_data_manager->getAllKeys();
×
312
    
NEW
313
    for (std::string const & key : data_keys) {
×
NEW
314
        auto data_variant = m_data_manager->getDataVariant(key);
×
NEW
315
        if (!data_variant.has_value()) {
×
NEW
316
            continue;
×
317
        }
318

319
        // Handle different data types
NEW
320
        std::visit([this, &key, entity_id](auto & data_ptr) {
×
321
            if constexpr (std::is_same_v<std::decay_t<decltype(data_ptr)>, std::shared_ptr<PointData>>) {
NEW
322
                if (data_ptr) {
×
NEW
323
                    removeEntityFromPointData(data_ptr.get(), entity_id);
×
324
                }
325
            } else if constexpr (std::is_same_v<std::decay_t<decltype(data_ptr)>, std::shared_ptr<LineData>>) {
NEW
326
                if (data_ptr) {
×
NEW
327
                    removeEntityFromLineData(data_ptr.get(), entity_id);
×
328
                }
329
            }
330
            // Note: DigitalEventSeries and DigitalIntervalSeries don't have entity lookup methods
331
            // so we skip them for now
NEW
332
        }, data_variant.value());
×
NEW
333
    }
×
NEW
334
}
×
335

NEW
336
void GroupManager::removeEntityFromPointData(PointData * point_data, EntityId entity_id) {
×
NEW
337
    if (!point_data) return;
×
338

339
    // Find the time and index for this entity
NEW
340
    auto time_and_index = point_data->getTimeAndIndexByEntityId(entity_id);
×
NEW
341
    if (!time_and_index.has_value()) {
×
NEW
342
        return;
×
343
    }
344

NEW
345
    auto const [time, index] = time_and_index.value();
×
346
    
347
    // Remove the point at the specific time and index
NEW
348
    point_data->clearAtTime(time, static_cast<size_t>(index), true);
×
349
}
350

NEW
351
void GroupManager::removeEntityFromLineData(LineData * line_data, EntityId entity_id) {
×
NEW
352
    if (!line_data) return;
×
353

354
    // Find the time and index for this entity
NEW
355
    auto time_and_index = line_data->getTimeAndIndexByEntityId(entity_id);
×
NEW
356
    if (!time_and_index.has_value()) {
×
NEW
357
        return;
×
358
    }
359

NEW
360
    auto const [time, index] = time_and_index.value();
×
361
    
362
    // Remove the line at the specific time and index
NEW
363
    line_data->clearAtTime(time, index, true);
×
364
}
365

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc