• 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

49.5
/src/WhiskerToolbox/GroupManagementWidget/GroupManagementWidget.cpp
1
#include "GroupManagementWidget.hpp"
2
#include "GroupManager.hpp"
3
#include "ui_GroupManagementWidget.h"
4

5
#include <QColorDialog>
6
#include <QDebug>
7
#include <QHeaderView>
8
#include <QPushButton>
9
#include <QTableWidget>
10
#include <QTableWidgetItem>
11
#include <QContextMenuEvent>
12
#include <QMenu>
13
#include <QAction>
14
#include <QMessageBox>
15
#include <QCheckBox>
16
#include <QDialog>
17
#include <QVBoxLayout>
18
#include <QHBoxLayout>
19
#include <QLabel>
20
#include <QListWidget>
21
#include <QListWidgetItem>
22
#include <QDialogButtonBox>
23

24
GroupManagementWidget::GroupManagementWidget(GroupManager * group_manager, QWidget * parent)
4✔
25
    : QWidget(parent),
26
      m_group_manager(group_manager),
4✔
27
      m_ui(new Ui::GroupManagementWidget()),
8✔
28
      m_updating_table(false) {
4✔
29

30
    m_ui->setupUi(this);
4✔
31
    setupUI();
4✔
32

33
    // Connect to GroupManager signals
34
    connect(m_group_manager, &GroupManager::groupCreated,
12✔
35
            this, &GroupManagementWidget::onGroupCreated);
8✔
36
    connect(m_group_manager, &GroupManager::groupRemoved,
12✔
37
            this, &GroupManagementWidget::onGroupRemoved);
8✔
38
    connect(m_group_manager, &GroupManager::groupModified,
12✔
39
            this, &GroupManagementWidget::onGroupModified);
8✔
40

41
    // Connect table signals
42
    connect(m_ui->groupsTable, &QTableWidget::itemChanged,
12✔
43
            this, &GroupManagementWidget::onItemChanged);
8✔
44
    connect(m_ui->groupsTable, &QTableWidget::itemSelectionChanged,
12✔
45
            this, &GroupManagementWidget::onSelectionChanged);
8✔
46

47
    // Connect button signals
48
    connect(m_ui->addButton, &QPushButton::clicked,
12✔
49
            this, &GroupManagementWidget::createNewGroup);
8✔
50
    connect(m_ui->removeButton, &QPushButton::clicked,
12✔
51
            this, &GroupManagementWidget::removeSelectedGroup);
8✔
52
    
53
    // Enable context menu
54
    m_ui->groupsTable->setContextMenuPolicy(Qt::CustomContextMenu);
4✔
55
    connect(m_ui->groupsTable, &QTableWidget::customContextMenuRequested,
12✔
56
            this, &GroupManagementWidget::showContextMenu);
8✔
57

58
    // Initial table refresh
59
    refreshTable();
4✔
60
    onSelectionChanged();// Update button states
4✔
61
}
4✔
62

63
GroupManagementWidget::~GroupManagementWidget() {
4✔
64
    delete m_ui;
4✔
65
}
4✔
66

67
void GroupManagementWidget::setupUI() {
4✔
68
    // Configure table headers
69
    QHeaderView * header = m_ui->groupsTable->horizontalHeader();
4✔
70
    header->setStretchLastSection(false);
4✔
71
    header->setSectionResizeMode(0, QHeaderView::Stretch);// Name column stretches
4✔
72
    header->setSectionResizeMode(1, QHeaderView::Fixed);  // Color column fixed width
4✔
73
    header->setSectionResizeMode(2, QHeaderView::Fixed);  // Visible column fixed width
4✔
74
    header->setSectionResizeMode(3, QHeaderView::Fixed);  // Members column fixed width
4✔
75
    m_ui->groupsTable->setColumnWidth(1, 50);             // Color button column width
4✔
76
    m_ui->groupsTable->setColumnWidth(2, 50);             // Visible checkbox column width
4✔
77
    m_ui->groupsTable->setColumnWidth(3, 60);             // Members column width
4✔
78

79
    m_ui->groupsTable->verticalHeader()->setVisible(false);
4✔
80
    
81
    // Enable multi-selection for group operations
82
    m_ui->groupsTable->setSelectionMode(QAbstractItemView::ExtendedSelection);
4✔
83
    m_ui->groupsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
4✔
84
}
4✔
85

86
void GroupManagementWidget::refreshTable() {
4✔
87
    m_updating_table = true;
4✔
88

89
    // Clear the table
90
    m_ui->groupsTable->setRowCount(0);
4✔
91

92
    // Add rows for all groups
93
    auto const & groups = m_group_manager->getGroups();
4✔
94
    int row = 0;
4✔
95
    for (auto it = groups.begin(); it != groups.end(); ++it) {
4✔
UNCOV
96
        addGroupRow(it.key(), row);
×
UNCOV
97
        row++;
×
98
    }
99

100
    m_updating_table = false;
4✔
101
}
8✔
102

103
void GroupManagementWidget::addGroupRow(int group_id, int row) {
5✔
104
    auto group = m_group_manager->getGroup(group_id);
5✔
105
    if (!group.has_value()) {
5✔
UNCOV
106
        return;
×
107
    }
108

109
    m_ui->groupsTable->insertRow(row);
5✔
110

111
    // Name column
112
    auto * name_item = new QTableWidgetItem(group.value().name);
5✔
113
    name_item->setData(Qt::UserRole, group_id);// Store group ID in the item
5✔
114
    m_ui->groupsTable->setItem(row, 0, name_item);
5✔
115

116
    // Color column
117
    QPushButton * color_button = createColorButton(group_id);
5✔
118
    updateColorButton(color_button, group.value().color);
5✔
119
    m_ui->groupsTable->setCellWidget(row, 1, color_button);
5✔
120

121
    // Visible column
122
    QCheckBox * visibility_checkbox = createVisibilityCheckbox(group_id);
5✔
123
    visibility_checkbox->setChecked(group.value().visible);
5✔
124
    m_ui->groupsTable->setCellWidget(row, 2, visibility_checkbox);
5✔
125

126
    // Members column
127
    int const member_count = m_group_manager->getGroupMemberCount(group_id);
5✔
128
    auto * members_item = new QTableWidgetItem(QString::number(member_count));
5✔
129
    members_item->setFlags(members_item->flags() & ~Qt::ItemIsEditable);// Make read-only
5✔
130
    members_item->setTextAlignment(Qt::AlignCenter);
5✔
131
    m_ui->groupsTable->setItem(row, 3, members_item);
5✔
132
}
5✔
133

134
QPushButton * GroupManagementWidget::createColorButton(int group_id) {
5✔
135
    auto * button = new QPushButton();
5✔
136
    button->setMaximumSize(30, 20);
5✔
137
    button->setMinimumSize(30, 20);
5✔
138
    button->setProperty("group_id", group_id);
5✔
139

140
    connect(button, &QPushButton::clicked,
15✔
141
            this, &GroupManagementWidget::onColorButtonClicked);
10✔
142

143
    return button;
5✔
144
}
145

146
QCheckBox * GroupManagementWidget::createVisibilityCheckbox(int group_id) {
5✔
147
    auto * checkbox = new QCheckBox();
5✔
148
    checkbox->setProperty("group_id", group_id);
5✔
149
    checkbox->setText(""); // No text, just the checkbox
5✔
150

151
    connect(checkbox, &QCheckBox::toggled,
15✔
152
            this, &GroupManagementWidget::onVisibilityToggled);
10✔
153

154
    return checkbox;
5✔
155
}
156

157
void GroupManagementWidget::updateColorButton(QPushButton * button, QColor const & color) {
18✔
158
    QString const style = QString("QPushButton { background-color: %1; border: 1px solid #666; }")
54✔
159
                            .arg(color.name());
36✔
160
    button->setStyleSheet(style);
18✔
161
}
36✔
162

UNCOV
163
void GroupManagementWidget::createNewGroup() {
×
UNCOV
164
    QString const name = QString("Group %1").arg(m_group_manager->getGroups().size() + 1);
×
UNCOV
165
    m_group_manager->createGroup(name);
×
UNCOV
166
}
×
167

UNCOV
168
void GroupManagementWidget::removeSelectedGroup() {
×
UNCOV
169
    int const current_row = m_ui->groupsTable->currentRow();
×
UNCOV
170
    if (current_row < 0) {
×
UNCOV
171
        return;
×
172
    }
173

UNCOV
174
    int const group_id = getGroupIdForRow(current_row);
×
UNCOV
175
    if (group_id != -1) {
×
UNCOV
176
        m_group_manager->removeGroup(group_id);
×
177
    }
178
}
179

180
void GroupManagementWidget::onGroupCreated(int group_id) {
5✔
181
    if (!m_updating_table) {
5✔
182
        int const row = m_ui->groupsTable->rowCount();
5✔
183
        addGroupRow(group_id, row);
5✔
184
    }
185
}
5✔
186

187
void GroupManagementWidget::onGroupRemoved(int group_id) {
1✔
188
    if (!m_updating_table) {
1✔
189
        int const row = findRowForGroupId(group_id);
1✔
190
        if (row >= 0) {
1✔
191
            m_ui->groupsTable->removeRow(row);
1✔
192
        }
193
    }
194
}
1✔
195

196
void GroupManagementWidget::onGroupModified(int group_id) {
13✔
197
    if (!m_updating_table) {
13✔
198
        int const row = findRowForGroupId(group_id);
13✔
199
        if (row >= 0) {
13✔
200
            auto group = m_group_manager->getGroup(group_id);
13✔
201
            if (group.has_value()) {
13✔
202
                // Update name
203
                QTableWidgetItem * name_item = m_ui->groupsTable->item(row, 0);
13✔
204
                if (name_item) {
13✔
205
                    m_updating_table = true;
13✔
206
                    name_item->setText(group.value().name);
13✔
207
                    m_updating_table = false;
13✔
208
                }
209

210
                // Update color button
211
                auto * color_button = qobject_cast<QPushButton *>(m_ui->groupsTable->cellWidget(row, 1));
13✔
212
                if (color_button) {
13✔
213
                    updateColorButton(color_button, group.value().color);
13✔
214
                }
215

216
                // Update visibility checkbox
217
                auto * visibility_checkbox = qobject_cast<QCheckBox *>(m_ui->groupsTable->cellWidget(row, 2));
13✔
218
                if (visibility_checkbox) {
13✔
219
                    visibility_checkbox->setChecked(group.value().visible);
8✔
220
                }
221

222
                // Update member count
223
                QTableWidgetItem * members_item = m_ui->groupsTable->item(row, 3);
13✔
224
                if (members_item) {
13✔
225
                    int const member_count = m_group_manager->getGroupMemberCount(group_id);
8✔
226
                    members_item->setText(QString::number(member_count));
8✔
227
                }
228
            }
229
        }
13✔
230
    }
231
}
13✔
232

233
void GroupManagementWidget::onPointAssignmentsChanged(std::unordered_set<int> const & affected_groups) {
×
234
    if (!m_updating_table) {
×
235
        // Update member counts for all affected groups
UNCOV
236
        for (int const group_id: affected_groups) {
×
237
            int const row = findRowForGroupId(group_id);
×
238
            if (row >= 0) {
×
239
                QTableWidgetItem * members_item = m_ui->groupsTable->item(row, 3);
×
240
                if (members_item) {
×
UNCOV
241
                    int const member_count = m_group_manager->getGroupMemberCount(group_id);
×
UNCOV
242
                    members_item->setText(QString::number(member_count));
×
243
                }
244
            }
245
        }
246
    }
247
}
×
248

249
void GroupManagementWidget::onItemChanged(QTableWidgetItem * item) {
15✔
250
    if (m_updating_table || !item || item->column() != 0) {
15✔
251
        return;// Only handle name column changes
10✔
252
    }
253

254
    int const group_id = item->data(Qt::UserRole).toInt();
5✔
255
    QString const new_name = item->text().trimmed();
5✔
256

257
    if (new_name.isEmpty()) {
5✔
258
        // Don't allow empty names, revert to original
UNCOV
259
        m_updating_table = true;
×
UNCOV
260
        auto group = m_group_manager->getGroup(group_id);
×
UNCOV
261
        if (group.has_value()) {
×
UNCOV
262
            item->setText(group.value().name);
×
263
        }
UNCOV
264
        m_updating_table = false;
×
265
        return;
×
UNCOV
266
    }
×
267

268
    // Update the group name
269
    m_group_manager->setGroupName(group_id, new_name);
5✔
270
}
5✔
271

272
void GroupManagementWidget::onColorButtonClicked() {
×
UNCOV
273
    auto * button = qobject_cast<QPushButton *>(sender());
×
274
    if (!button) {
×
275
        return;
×
276
    }
277

UNCOV
278
    int const group_id = button->property("group_id").toInt();
×
279
    auto group = m_group_manager->getGroup(group_id);
×
280
    if (!group.has_value()) {
×
281
        return;
×
282
    }
283

UNCOV
284
    QColor const new_color = QColorDialog::getColor(group.value().color, this, "Select Group Color");
×
285
    if (new_color.isValid() && new_color != group.value().color) {
×
UNCOV
286
        m_group_manager->setGroupColor(group_id, new_color);
×
287
    }
288
}
×
289

290
void GroupManagementWidget::onVisibilityToggled(bool visible) {
7✔
291
    auto * checkbox = qobject_cast<QCheckBox *>(sender());
7✔
292
    if (!checkbox) {
7✔
293
        return;
×
294
    }
295

296
    int const group_id = checkbox->property("group_id").toInt();
7✔
297
    m_group_manager->setGroupVisibility(group_id, visible);
7✔
298
}
299

300
void GroupManagementWidget::onSelectionChanged() {
4✔
301
    bool const has_selection = m_ui->groupsTable->currentRow() >= 0;
4✔
302
    m_ui->removeButton->setEnabled(has_selection);
4✔
303
}
4✔
304

305
int GroupManagementWidget::getGroupIdForRow(int row) const {
15✔
306
    QTableWidgetItem * item = m_ui->groupsTable->item(row, 0);
15✔
307
    return item ? item->data(Qt::UserRole).toInt() : -1;
30✔
308
}
309

310
int GroupManagementWidget::findRowForGroupId(int group_id) const {
14✔
311
    for (int row = 0; row < m_ui->groupsTable->rowCount(); ++row) {
15✔
312
        if (getGroupIdForRow(row) == group_id) {
15✔
313
            return row;
14✔
314
        }
315
    }
UNCOV
316
    return -1;
×
317
}
318

UNCOV
319
void GroupManagementWidget::contextMenuEvent(QContextMenuEvent * event) {
×
UNCOV
320
    showContextMenu(event->globalPos());
×
321
}
×
322

323
void GroupManagementWidget::showContextMenu(QPoint const & pos) {
×
324
    // Get selected rows
UNCOV
325
    auto selected_rows = m_ui->groupsTable->selectionModel()->selectedRows();
×
326
    if (selected_rows.isEmpty()) {
×
UNCOV
327
        return;
×
328
    }
329

330
    // Create context menu
UNCOV
331
    QMenu context_menu(this);
×
332
    
UNCOV
333
    QAction * merge_action = nullptr;
×
UNCOV
334
    QAction * delete_action = nullptr;
×
335
    
336
    // Add merge option if multiple groups are selected
UNCOV
337
    if (selected_rows.size() >= 2) {
×
UNCOV
338
        merge_action = context_menu.addAction("Merge Groups");
×
UNCOV
339
        merge_action->setIcon(QIcon::fromTheme("edit-merge"));
×
340
        
UNCOV
341
        context_menu.addSeparator();
×
342
    }
343
    
344
    // Add delete group and entities action (for single or multiple selection)
UNCOV
345
    delete_action = context_menu.addAction("Delete Group and All Data");
×
UNCOV
346
    delete_action->setIcon(QIcon::fromTheme("edit-delete"));
×
347
    
348
    // Show the menu and handle the action
UNCOV
349
    QAction * selected_action = context_menu.exec(m_ui->groupsTable->mapToGlobal(pos));
×
UNCOV
350
    if (selected_action == merge_action && selected_rows.size() >= 2) {
×
UNCOV
351
        showMergeDialog();
×
UNCOV
352
    } else if (selected_action == delete_action) {
×
UNCOV
353
        deleteSelectedGroupAndEntities();
×
354
    }
UNCOV
355
}
×
356

UNCOV
357
void GroupManagementWidget::deleteSelectedGroupAndEntities() {
×
358
    // Get selected rows
UNCOV
359
    auto selected_rows = m_ui->groupsTable->selectionModel()->selectedRows();
×
UNCOV
360
    if (selected_rows.isEmpty()) {
×
UNCOV
361
        return;
×
362
    }
363

364
    // Get group information for selected rows
UNCOV
365
    std::vector<std::pair<int, QString>> selected_groups;
×
UNCOV
366
    int total_members = 0;
×
367
    
UNCOV
368
    for (auto const & index : selected_rows) {
×
UNCOV
369
        int const group_id = getGroupIdForRow(index.row());
×
UNCOV
370
        if (group_id != -1) {
×
UNCOV
371
            auto group = m_group_manager->getGroup(group_id);
×
UNCOV
372
            if (group.has_value()) {
×
UNCOV
373
                selected_groups.emplace_back(group_id, group->name);
×
UNCOV
374
                total_members += m_group_manager->getGroupMemberCount(group_id);
×
375
            }
UNCOV
376
        }
×
377
    }
378

UNCOV
379
    if (selected_groups.empty()) {
×
UNCOV
380
        return;
×
381
    }
382

383
    // Create confirmation message
UNCOV
384
    QString message;
×
UNCOV
385
    if (selected_groups.size() == 1) {
×
UNCOV
386
        message = QString("Are you sure you want to delete group '%1' and all %2 entities in it?\n\nThis action cannot be undone.")
×
UNCOV
387
                    .arg(selected_groups[0].second)
×
UNCOV
388
                    .arg(total_members);
×
389
    } else {
UNCOV
390
        QString group_names;
×
UNCOV
391
        for (size_t i = 0; i < selected_groups.size(); ++i) {
×
UNCOV
392
            if (i > 0) group_names += ", ";
×
UNCOV
393
            group_names += "'" + selected_groups[i].second + "'";
×
394
        }
UNCOV
395
        message = QString("Are you sure you want to delete %1 groups (%2) and all %3 entities in them?\n\nThis action cannot be undone.")
×
UNCOV
396
                    .arg(selected_groups.size())
×
UNCOV
397
                    .arg(group_names)
×
UNCOV
398
                    .arg(total_members);
×
UNCOV
399
    }
×
400
    
401
    // Show confirmation dialog
UNCOV
402
    QMessageBox::StandardButton reply = QMessageBox::question(
×
403
        this,
404
        "Delete Groups and Data",
405
        message,
406
        QMessageBox::Yes | QMessageBox::No,
407
        QMessageBox::No
UNCOV
408
    );
×
409

UNCOV
410
    if (reply == QMessageBox::Yes) {
×
411
        // Delete all selected groups
UNCOV
412
        for (auto const & [group_id, group_name] : selected_groups) {
×
UNCOV
413
            m_group_manager->deleteGroupAndEntities(group_id);
×
414
        }
415
    }
UNCOV
416
}
×
417

418

UNCOV
419
void GroupManagementWidget::showMergeDialog() {
×
420
    // Get selected rows
UNCOV
421
    auto selected_rows = m_ui->groupsTable->selectionModel()->selectedRows();
×
UNCOV
422
    if (selected_rows.size() < 2) {
×
UNCOV
423
        return;
×
424
    }
425

426
    // Get group information for selected rows
UNCOV
427
    std::vector<std::pair<int, QString>> selected_groups;
×
UNCOV
428
    for (auto const & index : selected_rows) {
×
UNCOV
429
        int const group_id = getGroupIdForRow(index.row());
×
UNCOV
430
        if (group_id != -1) {
×
UNCOV
431
            auto group = m_group_manager->getGroup(group_id);
×
UNCOV
432
            if (group.has_value()) {
×
UNCOV
433
                selected_groups.emplace_back(group_id, group->name);
×
434
            }
UNCOV
435
        }
×
436
    }
437

UNCOV
438
    if (selected_groups.size() < 2) {
×
UNCOV
439
        return;
×
440
    }
441

442
    // Create merge dialog
UNCOV
443
    QDialog merge_dialog(this);
×
UNCOV
444
    merge_dialog.setWindowTitle("Merge Groups");
×
UNCOV
445
    merge_dialog.setModal(true);
×
446
    
UNCOV
447
    QVBoxLayout * layout = new QVBoxLayout(&merge_dialog);
×
448
    
UNCOV
449
    QLabel * instruction_label = new QLabel("Select the target group to merge into:");
×
UNCOV
450
    layout->addWidget(instruction_label);
×
451
    
UNCOV
452
    QListWidget * group_list = new QListWidget();
×
UNCOV
453
    for (auto const & [group_id, group_name] : selected_groups) {
×
UNCOV
454
        QListWidgetItem * item = new QListWidgetItem(group_name);
×
UNCOV
455
        item->setData(Qt::UserRole, group_id);
×
UNCOV
456
        group_list->addItem(item);
×
457
    }
UNCOV
458
    group_list->setSelectionMode(QAbstractItemView::SingleSelection);
×
UNCOV
459
    layout->addWidget(group_list);
×
460
    
UNCOV
461
    QHBoxLayout * button_layout = new QHBoxLayout();
×
UNCOV
462
    QPushButton * cancel_button = new QPushButton("Cancel");
×
UNCOV
463
    QPushButton * merge_button = new QPushButton("Merge");
×
UNCOV
464
    merge_button->setDefault(true);
×
465
    
UNCOV
466
    button_layout->addWidget(cancel_button);
×
UNCOV
467
    button_layout->addWidget(merge_button);
×
UNCOV
468
    layout->addLayout(button_layout);
×
469
    
470
    // Connect buttons
UNCOV
471
    connect(cancel_button, &QPushButton::clicked, &merge_dialog, &QDialog::reject);
×
UNCOV
472
    connect(merge_button, &QPushButton::clicked, &merge_dialog, &QDialog::accept);
×
473
    
474
    // Show dialog
UNCOV
475
    if (merge_dialog.exec() == QDialog::Accepted) {
×
UNCOV
476
        auto selected_items = group_list->selectedItems();
×
UNCOV
477
        if (!selected_items.isEmpty()) {
×
UNCOV
478
            int const target_group_id = selected_items.first()->data(Qt::UserRole).toInt();
×
479
            
480
            // Collect source group IDs (all except target)
UNCOV
481
            std::vector<int> source_group_ids;
×
UNCOV
482
            for (auto const & [group_id, group_name] : selected_groups) {
×
UNCOV
483
                if (group_id != target_group_id) {
×
UNCOV
484
                    source_group_ids.push_back(group_id);
×
485
                }
486
            }
487
            
488
            // Perform merge
UNCOV
489
            m_group_manager->mergeGroups(target_group_id, source_group_ids);
×
UNCOV
490
        }
×
UNCOV
491
    }
×
UNCOV
492
}
×
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