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

paulmthompson / WhiskerToolbox / 18389801194

09 Oct 2025 09:35PM UTC coverage: 71.943% (+0.1%) from 71.826%
18389801194

push

github

paulmthompson
add correlation matrix to filtering interface

207 of 337 new or added lines in 5 files covered. (61.42%)

867 existing lines in 31 files now uncovered.

49964 of 69449 relevant lines covered (71.94%)

1103.53 hits per line

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

14.1
/src/WhiskerToolbox/Media_Widget/MediaLine_Widget/MediaLine_Widget.cpp
1
#include "MediaLine_Widget.hpp"
2
#include "ui_MediaLine_Widget.h"
3

4
#include "CoreGeometry/point_geometry.hpp"
5
#include "DataManager/DataManager.hpp"
6
#include "DataManager/Lines/Line_Data.hpp"
7
#include "DataManager/Media/Media_Data.hpp"
8
#include "DataManager/Media/Video_Data.hpp"
9
#include "DataManager/transforms/Lines/Line_Angle/line_angle.hpp"
10
#include "DataManager/utils/polynomial/polynomial_fit.hpp"
11
#include "ImageProcessing/OpenCVUtility.hpp"
12
#include "Media_Widget/Media_Window/Media_Window.hpp"
13
#include "SelectionWidgets/LineAddSelectionWidget.hpp"
14
#include "SelectionWidgets/LineDrawAllFramesSelectionWidget.hpp"
15
#include "SelectionWidgets/LineEraseSelectionWidget.hpp"
16
#include "SelectionWidgets/LineNoneSelectionWidget.hpp"
17
#include "SelectionWidgets/LineSelectSelectionWidget.hpp"
18

19
#include <QButtonGroup>
20
#include <QCheckBox>
21
#include <QCursor>
22
#include <QGroupBox>
23
#include <QLabel>
24
#include <QMenu>
25
#include <QRadioButton>
26
#include <QSpinBox>
27
#include <QVBoxLayout>
28
#include <armadillo>
29
#include <iostream>
30
#include <opencv2/opencv.hpp>
31

32
MediaLine_Widget::MediaLine_Widget(std::shared_ptr<DataManager> data_manager, Media_Window * scene, QWidget * parent)
3✔
33
    : QWidget(parent),
34
      ui(new Ui::MediaLine_Widget),
6✔
35
      _data_manager{std::move(data_manager)},
3✔
36
      _scene{scene} {
6✔
37
    ui->setupUi(this);
3✔
38

39
    _selection_modes["(None)"] = Selection_Mode::None;
3✔
40
    _selection_modes["Select Line"] = Selection_Mode::Select;
3✔
41
    _selection_modes["Draw Across All Frames"] = Selection_Mode::DrawAllFrames;
3✔
42

43
    ui->selection_mode_combo->addItems(QStringList(_selection_modes.keys()));
3✔
44

45
    connect(ui->selection_mode_combo, &QComboBox::currentTextChanged, this, &MediaLine_Widget::_toggleSelectionMode);
3✔
46

47
    // Initialize the selection mode to match the current combo box selection
48
    _toggleSelectionMode(ui->selection_mode_combo->currentText());
3✔
49

50
    connect(ui->color_picker, &ColorPicker_Widget::colorChanged,
9✔
51
            this, &MediaLine_Widget::_setLineColor);
6✔
52
    connect(ui->color_picker, &ColorPicker_Widget::alphaChanged,
9✔
53
            this, &MediaLine_Widget::_setLineAlpha);
6✔
54

55
    // Connect line thickness controls
56
    connect(ui->line_thickness_slider, &QSlider::valueChanged,
9✔
57
            this, &MediaLine_Widget::_setLineThickness);
6✔
58
    connect(ui->line_thickness_spinbox, QOverload<int>::of(&QSpinBox::valueChanged),
9✔
59
            this, &MediaLine_Widget::_setLineThickness);
6✔
60

61
    // Synchronize line thickness slider and spinbox
62
    connect(ui->line_thickness_slider, &QSlider::valueChanged,
9✔
63
            ui->line_thickness_spinbox, &QSpinBox::setValue);
6✔
64
    connect(ui->line_thickness_spinbox, QOverload<int>::of(&QSpinBox::valueChanged),
9✔
65
            ui->line_thickness_slider, &QSlider::setValue);
6✔
66

67
    connect(ui->show_points_checkbox, &QCheckBox::toggled, this, &MediaLine_Widget::_toggleShowPoints);
3✔
68

69
    // Connect position marker controls
70
    connect(ui->show_position_marker_checkbox, &QCheckBox::toggled,
9✔
71
            this, &MediaLine_Widget::_toggleShowPositionMarker);
6✔
72
    connect(ui->position_percentage_slider, &QSlider::valueChanged,
9✔
73
            this, &MediaLine_Widget::_setPositionPercentage);
6✔
74
    connect(ui->position_percentage_spinbox, QOverload<int>::of(&QSpinBox::valueChanged),
9✔
75
            this, &MediaLine_Widget::_setPositionPercentage);
6✔
76

77
    // Synchronize position percentage slider and spinbox
78
    connect(ui->position_percentage_slider, &QSlider::valueChanged,
9✔
79
            ui->position_percentage_spinbox, &QSpinBox::setValue);
6✔
80
    connect(ui->position_percentage_spinbox, QOverload<int>::of(&QSpinBox::valueChanged),
9✔
81
            ui->position_percentage_slider, &QSlider::setValue);
6✔
82

83
    // Connect segment controls
84
    connect(ui->show_segment_checkbox, &QCheckBox::toggled,
9✔
85
            this, &MediaLine_Widget::_toggleShowSegment);
6✔
86
    connect(ui->segment_start_slider, &QSlider::valueChanged,
9✔
87
            this, &MediaLine_Widget::_setSegmentStartPercentage);
6✔
88
    connect(ui->segment_start_spinbox, QOverload<int>::of(&QSpinBox::valueChanged),
9✔
89
            this, &MediaLine_Widget::_setSegmentStartPercentage);
6✔
90
    connect(ui->segment_end_slider, &QSlider::valueChanged,
9✔
91
            this, &MediaLine_Widget::_setSegmentEndPercentage);
6✔
92
    connect(ui->segment_end_spinbox, QOverload<int>::of(&QSpinBox::valueChanged),
9✔
93
            this, &MediaLine_Widget::_setSegmentEndPercentage);
6✔
94

95
    // Note: Removed direct slider-spinbox synchronization to prevent infinite loops
96
    // The synchronization is now handled within the percentage setter functions
97

98

99
    _setupSelectionModePages();
3✔
100
}
3✔
101

102
void MediaLine_Widget::_setupSelectionModePages() {
3✔
103
    _noneSelectionWidget = new line_widget::LineNoneSelectionWidget();
3✔
104
    ui->mode_stacked_widget->addWidget(_noneSelectionWidget);
3✔
105

106
    _addSelectionWidget = new line_widget::LineAddSelectionWidget();
3✔
107
    ui->mode_stacked_widget->addWidget(_addSelectionWidget);
3✔
108

109
    connect(_addSelectionWidget, &line_widget::LineAddSelectionWidget::edgeSnappingToggled,
9✔
110
            this, &MediaLine_Widget::_toggleEdgeSnapping);
6✔
111
    connect(_addSelectionWidget, &line_widget::LineAddSelectionWidget::smoothingModeChanged,
9✔
112
            this, &MediaLine_Widget::_setSmoothingMode);
6✔
113
    connect(_addSelectionWidget, &line_widget::LineAddSelectionWidget::polynomialOrderChanged,
9✔
114
            this, &MediaLine_Widget::_setPolynomialOrder);
6✔
115
    connect(_addSelectionWidget, &line_widget::LineAddSelectionWidget::edgeThresholdChanged,
9✔
116
            this, &MediaLine_Widget::_setEdgeThreshold);
6✔
117
    connect(_addSelectionWidget, &line_widget::LineAddSelectionWidget::edgeSearchRadiusChanged,
9✔
118
            this, &MediaLine_Widget::_setEdgeSearchRadius);
6✔
119

120
    _eraseSelectionWidget = new line_widget::LineEraseSelectionWidget();
3✔
121
    ui->mode_stacked_widget->addWidget(_eraseSelectionWidget);
3✔
122

123
    connect(_eraseSelectionWidget, &line_widget::LineEraseSelectionWidget::eraserRadiusChanged,
9✔
124
            this, &MediaLine_Widget::_setEraserRadius);
6✔
125
    connect(_eraseSelectionWidget, &line_widget::LineEraseSelectionWidget::showCircleToggled,
9✔
126
            this, &MediaLine_Widget::_toggleShowHoverCircle);
6✔
127

128
    _selectSelectionWidget = new line_widget::LineSelectSelectionWidget();
3✔
129
    ui->mode_stacked_widget->addWidget(_selectSelectionWidget);
3✔
130

131
    connect(_selectSelectionWidget, &line_widget::LineSelectSelectionWidget::selectionThresholdChanged,
9✔
132
            this, [this](float threshold) {
6✔
UNCOV
133
                _line_selection_threshold = threshold;
×
UNCOV
134
                std::cout << "Line selection threshold set to: " << threshold << std::endl;
×
UNCOV
135
            });
×
136

137
    _drawAllFramesSelectionWidget = new line_widget::LineDrawAllFramesSelectionWidget();
3✔
138
    ui->mode_stacked_widget->addWidget(_drawAllFramesSelectionWidget);
3✔
139

140
    connect(_drawAllFramesSelectionWidget, &line_widget::LineDrawAllFramesSelectionWidget::lineDrawingStarted,
9✔
141
            this, [this]() {
6✔
UNCOV
142
                std::cout << "Line drawing started for all frames mode" << std::endl;
×
UNCOV
143
            });
×
144
    connect(_drawAllFramesSelectionWidget, &line_widget::LineDrawAllFramesSelectionWidget::lineDrawingCompleted,
9✔
145
            this, [this]() {
6✔
146
                std::cout << "Line drawing completed for all frames mode" << std::endl;
×
UNCOV
147
            });
×
148
    connect(_drawAllFramesSelectionWidget, &line_widget::LineDrawAllFramesSelectionWidget::applyToAllFrames,
9✔
149
            this, &MediaLine_Widget::_applyLineToAllFrames);
6✔
150
    connect(_drawAllFramesSelectionWidget, &line_widget::LineDrawAllFramesSelectionWidget::linePointsUpdated,
9✔
151
            this, &MediaLine_Widget::_updateTemporaryLineFromWidget);
6✔
152

153
    ui->mode_stacked_widget->setCurrentIndex(0);
3✔
154
}
3✔
155

156
MediaLine_Widget::~MediaLine_Widget() {
6✔
157
    delete ui;
3✔
158
}
6✔
159

UNCOV
160
void MediaLine_Widget::showEvent(QShowEvent * event) {
×
161

162
    static_cast<void>(event);
163

UNCOV
164
    std::cout << "Show Event" << std::endl;
×
165
    
166
    // Debug: Check initial selection state
167
    auto initial_selections = _scene->getSelectedEntities();
×
168
    std::cout << "Debug: Initial selected entities on show: " << initial_selections.size() << std::endl;
×
169
    
170
    connect(_scene, &Media_Window::leftClickMediaWithEvent, this, &MediaLine_Widget::_clickedInVideoWithModifiers);
×
UNCOV
171
    connect(_scene, &Media_Window::rightClickMedia, this, &MediaLine_Widget::_rightClickedInVideo);
×
UNCOV
172
    connect(_scene, &Media_Window::mouseMove, this, [this](qreal x, qreal y) {
×
UNCOV
173
        _mouseMoved(x, y);
×
UNCOV
174
    });
×
UNCOV
175
}
×
176

177
void MediaLine_Widget::hideEvent(QHideEvent * event) {
3✔
178

179
    static_cast<void>(event);
180

181
    std::cout << "Hide Event" << std::endl;
3✔
182
    disconnect(_scene, &Media_Window::leftClickMediaWithEvent, this, &MediaLine_Widget::_clickedInVideoWithModifiers);
3✔
183
    disconnect(_scene, &Media_Window::rightClickMedia, this, &MediaLine_Widget::_rightClickedInVideo);
3✔
184
    disconnect(_scene, &Media_Window::mouseMove, this, nullptr);
3✔
185

186
    // Clean up hover circle when switching away from line widget
187
    _scene->setShowHoverCircle(false);
3✔
188
    
189
    // Note: We don't disable group selection here to preserve selections
190
}
3✔
191

192
void MediaLine_Widget::setActiveKey(std::string const & key) {
×
193
    _active_key = key;
×
194
    ui->name_label->setText(QString::fromStdString(key));
×
195

196
    // Set the color picker to the current line color if available
197
    if (!key.empty()) {
×
198
        auto config = _scene->getLineConfig(key);
×
199

200
        if (config) {
×
201
            ui->color_picker->setColor(QString::fromStdString(config.value()->hex_color));
×
202
            ui->color_picker->setAlpha(static_cast<int>(config.value()->alpha * 100));
×
203

204
            // Set line thickness controls
205
            ui->line_thickness_slider->blockSignals(true);
×
206
            ui->line_thickness_spinbox->blockSignals(true);
×
207
            ui->line_thickness_slider->setValue(config.value()->line_thickness);
×
UNCOV
208
            ui->line_thickness_spinbox->setValue(config.value()->line_thickness);
×
UNCOV
209
            ui->line_thickness_slider->blockSignals(false);
×
210
            ui->line_thickness_spinbox->blockSignals(false);
×
211

212
            // Update the show points checkbox directly from the UI file
UNCOV
213
            ui->show_points_checkbox->blockSignals(true);
×
214
            ui->show_points_checkbox->setChecked(config.value()->show_points);
×
215
            ui->show_points_checkbox->blockSignals(false);
×
216

217
            // Set position marker controls
218
            ui->show_position_marker_checkbox->blockSignals(true);
×
219
            ui->show_position_marker_checkbox->setChecked(config.value()->show_position_marker);
×
UNCOV
220
            ui->show_position_marker_checkbox->blockSignals(false);
×
221

222
            ui->position_percentage_slider->blockSignals(true);
×
223
            ui->position_percentage_spinbox->blockSignals(true);
×
224
            ui->position_percentage_slider->setValue(config.value()->position_percentage);
×
UNCOV
225
            ui->position_percentage_spinbox->setValue(config.value()->position_percentage);
×
226
            ui->position_percentage_slider->blockSignals(false);
×
227
            ui->position_percentage_spinbox->blockSignals(false);
×
228

229
            // Set segment controls
230
            ui->show_segment_checkbox->blockSignals(true);
×
231
            ui->show_segment_checkbox->setChecked(config.value()->show_segment);
×
UNCOV
232
            ui->show_segment_checkbox->blockSignals(false);
×
233

234
            ui->segment_start_slider->blockSignals(true);
×
235
            ui->segment_start_spinbox->blockSignals(true);
×
236
            ui->segment_start_slider->setValue(config.value()->segment_start_percentage);
×
237
            ui->segment_start_spinbox->setValue(config.value()->segment_start_percentage);
×
238
            ui->segment_start_slider->blockSignals(false);
×
UNCOV
239
            ui->segment_start_spinbox->blockSignals(false);
×
240

241
            ui->segment_end_slider->blockSignals(true);
×
UNCOV
242
            ui->segment_end_spinbox->blockSignals(true);
×
243
            ui->segment_end_slider->setValue(config.value()->segment_end_percentage);
×
244
            ui->segment_end_spinbox->setValue(config.value()->segment_end_percentage);
×
245
            ui->segment_end_slider->blockSignals(false);
×
246
            ui->segment_end_spinbox->blockSignals(false);
×
247

248
            // Reset line selection
249
            _current_line_index = 0;
×
250
        }
251
    }
252
}
×
253

254
void MediaLine_Widget::_setLineAlpha(int alpha) {
×
255
    float const alpha_float = static_cast<float>(alpha) / 100;
×
256

257
    if (!_active_key.empty()) {
×
UNCOV
258
        auto line_opts = _scene->getLineConfig(_active_key);
×
259
        if (line_opts.has_value()) {
×
UNCOV
260
            line_opts.value()->alpha = alpha_float;
×
261
        }
262
        _scene->UpdateCanvas();
×
263
    }
264
}
×
265

266
void MediaLine_Widget::_setLineColor(QString const & hex_color) {
×
267
    if (!_active_key.empty()) {
×
UNCOV
268
        auto line_opts = _scene->getLineConfig(_active_key);
×
269
        if (line_opts.has_value()) {
×
UNCOV
270
            line_opts.value()->hex_color = hex_color.toStdString();
×
271
        }
UNCOV
272
        _scene->UpdateCanvas();
×
273
    }
274
}
×
275

276
void MediaLine_Widget::_clickedInVideoWithModifiers(qreal x_canvas, qreal y_canvas, Qt::KeyboardModifiers modifiers) {
×
277
    if (_active_key.empty()) {
×
UNCOV
278
        std::cout << "No active key" << std::endl;
×
279
        return;
×
280
    }
281

UNCOV
282
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
283
    if (!line_data) {
×
284
        std::cout << "No line data for active key" << std::endl;
×
285
        return;
×
286
    }
287

UNCOV
288
    auto const x_media = static_cast<float>(x_canvas);
×
289
    auto const y_media = static_cast<float>(y_canvas);
×
290
    auto const current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
291

292
    switch (_selection_mode) {
×
UNCOV
293
        case Selection_Mode::None: {
×
UNCOV
294
            std::cout << "Selection mode is None" << std::endl;
×
295
            break;
×
296
        }
297
        case Selection_Mode::Select: {
×
298
            std::cout << "Selection mode is Select" << std::endl;
×
299
            
300
            // Check for modifier keys to determine action
UNCOV
301
            if (modifiers & Qt::ControlModifier) {
×
302
                // Ctrl+click: Add points to selected line
303
                std::cout << "Ctrl+click: Adding points to selected line" << std::endl;
×
304
                _addPointToLine(x_media, y_media, current_time);
×
305
            } else if (modifiers & Qt::AltModifier) {
×
306
                // Alt+click: Erase points from selected line
307
                std::cout << "Alt+click: Erasing points from selected line" << std::endl;
×
308
                _erasePointsFromLine(x_media, y_media, current_time);
×
309
            } else {
310
                // Normal click: Select/deselect lines
UNCOV
311
                QPointF scene_pos(x_canvas * _scene->getXAspect(), y_canvas * _scene->getYAspect());
×
312
                std::string data_key, data_type;
×
313
                EntityId entity_id = _scene->findEntityAtPosition(scene_pos, data_key, data_type);
×
314
                
315
                if (entity_id != 0 && data_type == "line" && data_key == _active_key) {
×
316
                    // Use the group-based selection system for consistency
317
                    _scene->selectEntity(entity_id, data_key, data_type);
×
318
                    std::cout << "Selected line entity " << entity_id << " in group system" << std::endl;
×
319
                } else {
320
                    // Clear selections if no line found
321
                    _scene->clearAllSelections();
×
322
                    std::cout << "No line found within threshold - cleared selections" << std::endl;
×
323
                }
UNCOV
324
            }
×
325
            break;
×
326
        }
327
        case Selection_Mode::DrawAllFrames: {
×
328
            std::cout << "Selection mode is DrawAllFrames" << std::endl;
×
UNCOV
329
            _addPointToDrawAllFrames(x_media, y_media);
×
UNCOV
330
            break;
×
331
        }
332
    }
UNCOV
333
}
×
334

335
void MediaLine_Widget::_mouseMoved(qreal x, qreal y) {
×
336
    // Only handle mouse move in Select Line mode
337
    if (_selection_mode != Selection_Mode::Select) {
×
338
        return;
×
339
    }
340
    
341
    // Check if Alt is currently held (we can't get modifier state from mouse move,
342
    // so we'll track it from the last click event)
343
    // For now, we'll show the eraser circle when in Select mode
344
    // This could be improved by tracking modifier state
345
    static bool alt_held = false;
346
    
347
    // Update hover circle position and show/hide based on modifier state
UNCOV
348
    if (alt_held) {
×
349
        _scene->setShowHoverCircle(true);
×
350
        if (_eraseSelectionWidget) {
×
351
            _scene->setHoverCircleRadius(_eraseSelectionWidget->getEraserRadius());
×
352
        }
353
    } else {
UNCOV
354
        _scene->setShowHoverCircle(false);
×
355
    }
356
}
357

358
void MediaLine_Widget::_addPointToLine(float x_media, float y_media, TimeFrameIndex current_time) {
×
359
    // Check if we have a selected line from the group system
UNCOV
360
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
361
    if (selected_line_index < 0) {
×
362
        std::cout << "No line selected - cannot add points" << std::endl;
×
363
        return;
×
364
    }
365

366
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
UNCOV
367
    if (!line_data) {
×
368
        std::cout << "No line data for active key" << std::endl;
×
UNCOV
369
        return;
×
370
    }
371

372
    // Get the EntityID for the selected line
373
    auto selected_entities = _scene->getSelectedEntities();
×
UNCOV
374
    if (selected_entities.empty()) {
×
375
        std::cout << "No selected entities" << std::endl;
×
376
        return;
×
377
    }
378
    
379
    EntityId selected_entity_id = *selected_entities.begin();
×
380
    
381
    // Get a mutable reference to the line
UNCOV
382
    auto line_ref = line_data->getMutableLineByEntityId(selected_entity_id);
×
UNCOV
383
    if (!line_ref.has_value()) {
×
UNCOV
384
        std::cout << "Could not get mutable reference to line with EntityID " << selected_entity_id << std::endl;
×
385
        return;
×
386
    }
387
    
388
    Line2D& line = line_ref.value().get();
×
389

390
    // Check if edge snapping is enabled
UNCOV
391
    bool use_edge_snapping = false;
×
392
    auto line_opts = _scene->getLineConfig(_active_key);
×
393
    if (line_opts.has_value()) {
×
394
        use_edge_snapping = line_opts.value()->edge_snapping;
×
395
    }
396

397
    if (use_edge_snapping && _edge_snapping_enabled) {
×
398
        if (_current_edges.empty()) {
×
399
            _detectEdges();
×
400
        }
401

402
        auto edge_point = _findNearestEdge(x_media, y_media);
×
403
        x_media = edge_point.first;
×
UNCOV
404
        y_media = edge_point.second;
×
405
    }
406

UNCOV
407
    if (_smoothing_mode == Smoothing_Mode::SimpleSmooth) {
×
408
        // Use the original smoothing approach - add point directly
409
        line.push_back(Point2D<float>{x_media, y_media});
×
UNCOV
410
    } else if (_smoothing_mode == Smoothing_Mode::PolynomialFit) {
×
411
        // If the line already exists, add interpolated points between the last point and the new point
412
        if (!line.empty()) {
×
413
            Point2D<float> const last_point = line.back();
×
414

415
            // Calculate distance between last point and new point
UNCOV
416
            float dx = x_media - last_point.x;
×
417
            float dy = y_media - last_point.y;
×
418
            float distance = std::sqrt(dx * dx + dy * dy);
×
419

420
            // Add interpolated points if the distance is significant
421
            if (distance > 5.0f) {// Threshold for adding interpolation
×
422
                int num_interp_points = std::max(2, static_cast<int>(distance / 5.0f));
×
UNCOV
423
                for (int i = 1; i <= num_interp_points; ++i) {
×
424
                    float t = static_cast<float>(i) / (num_interp_points + 1);
×
UNCOV
425
                    float interp_x = last_point.x + t * dx;
×
UNCOV
426
                    float interp_y = last_point.y + t * dy;
×
427
                    line.push_back(Point2D<float>{interp_x, interp_y});
×
428
                }
429
            }
430
        }
431

432
        // Add the actual new point
433
        line.push_back(Point2D<float>{x_media, y_media});
×
434

435
        // Apply polynomial fitting if we have enough points
UNCOV
436
        if (line.size() >= 3) {
×
UNCOV
437
            _applyPolynomialFit(line, _polynomial_order);
×
438
        }
439
    }
440

441
    // Notify observers that the data has changed
UNCOV
442
    line_data->notifyObservers();
×
443

444
    _scene->UpdateCanvas();
×
445
    std::cout << "Added point (" << x_media << ", " << y_media << ") to line "
×
446
              << _active_key << " (EntityID: " << selected_entity_id << ")" << std::endl;
×
447
}
×
448

UNCOV
449
void MediaLine_Widget::_erasePointsFromLine(float x_media, float y_media, TimeFrameIndex current_time) {
×
450
    // Check if we have a selected line from the group system
451
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
452
    if (selected_line_index < 0) {
×
UNCOV
453
        std::cout << "No line selected - cannot erase points" << std::endl;
×
454
        return;
×
455
    }
456

457
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
UNCOV
458
    if (!line_data) {
×
UNCOV
459
        std::cout << "No line data for active key" << std::endl;
×
UNCOV
460
        return;
×
461
    }
462

463
    // Get the EntityID for the selected line
UNCOV
464
    auto selected_entities = _scene->getSelectedEntities();
×
465
    if (selected_entities.empty()) {
×
466
        std::cout << "No selected entities" << std::endl;
×
UNCOV
467
        return;
×
468
    }
469
    
470
    EntityId selected_entity_id = *selected_entities.begin();
×
471
    
472
    // Get a mutable reference to the line
UNCOV
473
    auto line_ref = line_data->getMutableLineByEntityId(selected_entity_id);
×
UNCOV
474
    if (!line_ref.has_value()) {
×
UNCOV
475
        std::cout << "Could not get mutable reference to line with EntityID " << selected_entity_id << std::endl;
×
476
        return;
×
477
    }
478
    
479
    Line2D& line = line_ref.value().get();
×
480
    
481
    if (line.empty()) {
×
482
        std::cout << "Selected line is empty - nothing to erase" << std::endl;
×
UNCOV
483
        return;
×
484
    }
485

486
    // Get eraser radius from the erase selection widget
487
    float eraser_radius = 10.0f; // Default radius
×
UNCOV
488
    if (_eraseSelectionWidget) {
×
UNCOV
489
        eraser_radius = static_cast<float>(_eraseSelectionWidget->getEraserRadius());
×
490
    }
491

492
    // Find points within eraser radius and remove them
UNCOV
493
    std::vector<Point2D<float>> remaining_points;
×
UNCOV
494
    Point2D<float> click_point{x_media, y_media};
×
495
    
UNCOV
496
    for (auto const & point : line) {
×
UNCOV
497
        float distance = calc_distance(click_point, point);
×
UNCOV
498
        if (distance > eraser_radius) {
×
UNCOV
499
            remaining_points.push_back(point);
×
500
        }
501
    }
502

503
    // Update the line with remaining points (this modifies the original line)
504
    line = Line2D(remaining_points);
×
505

506
    // Notify observers that the data has changed
UNCOV
507
    line_data->notifyObservers();
×
508

UNCOV
509
    _scene->UpdateCanvas();
×
UNCOV
510
    std::cout << "Erased points near (" << x_media << ", " << y_media << ") from line "
×
UNCOV
511
              << _active_key << " (EntityID: " << selected_entity_id << ")" << std::endl;
×
512
}
×
513

UNCOV
514
void MediaLine_Widget::_applyPolynomialFit(Line2D & line, int order) {
×
UNCOV
515
    if (line.size() < static_cast<size_t>(order + 1)) {
×
516
        // Not enough points for the requested polynomial order
UNCOV
517
        return;
×
518
    }
519

520
    // Extract x and y coordinates
521
    std::vector<double> t(line.size());
×
522
    std::vector<double> x_coords(line.size());
×
UNCOV
523
    std::vector<double> y_coords(line.size());
×
524

525
    // Use parameter t along the curve (0 to 1)
526
    for (size_t i = 0; i < line.size(); ++i) {
×
UNCOV
527
        t[i] = static_cast<double>(i) / (line.size() - 1);
×
528
        x_coords[i] = line[i].x;
×
529
        y_coords[i] = line[i].y;
×
530
    }
531

532
    // Fit polynomials to x(t) and y(t) using the function from line_angle.hpp
533
    std::vector<double> x_coeffs = fit_polynomial(t, x_coords, order);
×
534
    std::vector<double> y_coeffs = fit_polynomial(t, y_coords, order);
×
535

UNCOV
536
    if (x_coeffs.empty() || y_coeffs.empty()) {
×
537
        // Fall back to simple smoothing if fitting failed
538
        smooth_line(line);
×
539
        return;
×
540
    }
541

542
    // Generate smooth curve with more points
UNCOV
543
    int const num_points = std::max(100, static_cast<int>(line.size()) * 2);
×
UNCOV
544
    std::vector<Point2D<float>> smooth_line;
×
545
    smooth_line.reserve(num_points);
×
546

UNCOV
547
    for (int i = 0; i < num_points; ++i) {
×
548
        double t_param = static_cast<double>(i) / (num_points - 1);
×
549

550
        // Evaluate polynomials at t_param using the function from line_angle.hpp
551
        double x_val = evaluate_polynomial(x_coeffs, t_param);
×
UNCOV
552
        double y_val = evaluate_polynomial(y_coeffs, t_param);
×
553

UNCOV
554
        smooth_line.push_back(Point2D<float>{static_cast<float>(x_val), static_cast<float>(y_val)});
×
555
    }
556

557
    // Replace the original line with the smooth one
558
    line = Line2D(smooth_line);
×
559
}
×
560

561
void MediaLine_Widget::_setSmoothingMode(int index) {
×
UNCOV
562
    _smoothing_mode = static_cast<Smoothing_Mode>(index);
×
UNCOV
563
    std::cout << "Smoothing mode set to: " << index << std::endl;
×
564
}
×
565

UNCOV
566
void MediaLine_Widget::_setPolynomialOrder(int order) {
×
UNCOV
567
    _polynomial_order = order;
×
568
    std::cout << "Polynomial order set to: " << order << std::endl;
×
UNCOV
569
}
×
570

571
void MediaLine_Widget::_toggleSelectionMode(QString text) {
3✔
572
    _selection_mode = _selection_modes[text];
3✔
573
    std::cout << "MediaLine_Widget: Selection mode changed to: " << text.toStdString() 
6✔
574
              << " (enum value: " << static_cast<int>(_selection_mode) << ")" << std::endl;
6✔
575

576
    // Switch to the appropriate page in the stacked widget
577
    int pageIndex = static_cast<int>(_selection_mode);
3✔
578
    ui->mode_stacked_widget->setCurrentIndex(pageIndex);
3✔
579
    
580
    // For Select Line mode, show both add and erase options
581
    if (_selection_mode == Selection_Mode::Select) {
3✔
582
        // Show both add and erase widgets by creating a combined layout
583
        // For now, we'll show the add widget as the primary options
584
        // The erase options will be available through the existing erase widget
585
    }
586

587
    // Always enable group selection for line operations
588
    // This prevents selections from being cleared when switching modes
589
    _scene->setGroupSelectionEnabled(true);
3✔
590
    std::cout << "MediaLine_Widget: Group selection enabled for line operations" << std::endl;
3✔
591
    
592
    // Debug: Check if we have any selections after mode change
593
    auto selected_entities = _scene->getSelectedEntities();
3✔
594
    std::cout << "Debug: Selected entities after mode change: " << selected_entities.size() << std::endl;
3✔
595

596
    if (_selection_mode == Selection_Mode::Select) {
3✔
597
        // Show hover circle for eraser when in Select mode (will be controlled by Shift key)
UNCOV
598
        _scene->setShowHoverCircle(false); // Initially off, will be controlled by mouse move events
×
599
    } else {
600
        _scene->setShowHoverCircle(false);
3✔
601
    }
602

603
    // Enable/disable temporary line visualization for DrawAllFrames mode
604
    if (_selection_mode == Selection_Mode::DrawAllFrames) {
3✔
605
        _scene->setShowTemporaryLine(true);
×
606
    } else {
607
        _scene->setShowTemporaryLine(false);
3✔
608
    }
609
}
6✔
610

UNCOV
611
void MediaLine_Widget::_toggleShowPoints(bool checked) {
×
612
    if (!_active_key.empty()) {
×
613
        auto line_opts = _scene->getLineConfig(_active_key);
×
UNCOV
614
        if (line_opts.has_value()) {
×
UNCOV
615
            line_opts.value()->show_points = checked;
×
616
        }
617
        _scene->UpdateCanvas();
×
618
    }
619
}
×
620

621
void MediaLine_Widget::_toggleEdgeSnapping(bool checked) {
×
622
    _edge_snapping_enabled = checked;
×
623

624
    if (!_active_key.empty()) {
×
UNCOV
625
        auto line_opts = _scene->getLineConfig(_active_key);
×
626
        if (line_opts.has_value()) {
×
UNCOV
627
            line_opts.value()->edge_snapping = checked;
×
628
        }
629

630
        // If enabling edge snapping, perform edge detection immediately
631
        if (checked) {
×
UNCOV
632
            _detectEdges();
×
633
        } else {
634
            // Clear cached edges when disabling
635
            _current_edges.release();
×
636
        }
637

UNCOV
638
        _scene->UpdateCanvas();
×
639
    }
640

UNCOV
641
    std::cout << "Edge snapping " << (checked ? "enabled" : "disabled") << std::endl;
×
UNCOV
642
}
×
643

UNCOV
644
void MediaLine_Widget::LoadFrame(int frame_id) {
×
645
    // Update the widget with the new frame
646
    // This could involve refreshing displays or updating UI elements
647
    // specific to the current frame
648

649
    // If we have an active line, we might want to update some UI
650
    // based on line data at this frame
UNCOV
651
    if (!_active_key.empty()) {
×
UNCOV
652
        auto line_data = _data_manager->getData<LineData>(_active_key);
×
UNCOV
653
        if (line_data) {
×
UNCOV
654
            auto lines = line_data->getAtTime(TimeFrameIndex(frame_id));
×
UNCOV
655
            int num_lines = static_cast<int>(lines.size());
×
656

657
            std::cout << "Frame " << frame_id << ": " << num_lines << " lines in " << _active_key << std::endl;
×
UNCOV
658
        }
×
UNCOV
659
    }
×
UNCOV
660
}
×
661

662

663
void MediaLine_Widget::_setEdgeThreshold(int threshold) {
×
UNCOV
664
    _edge_threshold = threshold;
×
665
    std::cout << "Edge threshold set to: " << threshold << std::endl;
×
666
}
×
667

UNCOV
668
void MediaLine_Widget::_setEdgeSearchRadius(int radius) {
×
669
    _edge_search_radius = radius;
×
670
    std::cout << "Edge search radius set to: " << radius << std::endl;
×
671
}
×
672

UNCOV
673
void MediaLine_Widget::_detectEdges() {
×
674

675
    auto media = _data_manager->getData<MediaData>("media");
×
676
    if (!media) {
×
UNCOV
677
        std::cout << "No media data available for edge detection" << std::endl;
×
UNCOV
678
        return;
×
679
    }
680

681
    auto const current_time = _data_manager->getCurrentTime();
×
UNCOV
682
    auto frame_data = media->getProcessedData(current_time);
×
683

684
    // Convert raw frame data to cv::Mat
685
    /*
686
    int width = media->getWidth();
687
    int height = media->getHeight();
688
    
689
    // Create a grayscale image for edge detection
690
    cv::Mat gray_image;
691
    
692
    // Check the format of media data and create appropriate cv::Mat
693
    if (media->getFormat() == MediaData::DisplayFormat::Gray) {
694
        // Grayscale image
695
        gray_image = cv::Mat(height, width, CV_8UC1, frame_data.data());
696
    } else {
697
        // Color image (RGBA)
698
        _current_frame = cv::Mat(height, width, CV_8UC4, frame_data.data());
699
        
700
        // Convert to grayscale
701
        cv::cvtColor(_current_frame, gray_image, cv::COLOR_RGBA2GRAY);
702
    }
703
    */
UNCOV
704
    auto gray_image = ImageProcessing::convert_vector_to_mat(frame_data, media->getImageSize());
×
705

706
    //cv::Mat blurred;
707
    //cv::GaussianBlur(gray_image, blurred, cv::Size(5, 5), 1.5);
708
    //cv::Canny(blurred, _current_edges, _edge_threshold / 2, _edge_threshold);
709

710
    cv::Canny(gray_image, _current_edges, _edge_threshold / 2, _edge_threshold);
×
711

712
    std::cout << "Edge detection completed for frame " << current_time << std::endl;
×
UNCOV
713
    std::cout << "Edges detected: " << _current_edges.size() << std::endl;
×
UNCOV
714
}
×
715

UNCOV
716
std::pair<float, float> MediaLine_Widget::_findNearestEdge(float x, float y) {
×
UNCOV
717
    if (_current_edges.empty()) {
×
718
        return {x, y};
×
719
    }
720

721
    // Round x and y to integers (image coordinates)
722
    int x_int = static_cast<int>(std::round(x));
×
723
    int y_int = static_cast<int>(std::round(y));
×
724

725
    // Define search radius and initialize variables
726
    int radius = _edge_search_radius;
×
UNCOV
727
    float min_distance = radius * radius + 1;     // Initialize to something larger than possible
×
UNCOV
728
    std::pair<float, float> nearest_edge = {x, y};// Default to original point
×
729

730
    // Get image dimensions
731
    int width = _current_edges.cols;
×
UNCOV
732
    int height = _current_edges.rows;
×
733

734
    // Check if the point is within image bounds
UNCOV
735
    if (x_int < 0 || x_int >= width || y_int < 0 || y_int >= height) {
×
736
        std::cout << "Click point outside image bounds" << std::endl;
×
737
        return {x, y};
×
738
    }
739

740
    // Search in a square region around the clicked point
741
    for (int dy = -radius; dy <= radius; dy++) {
×
742
        for (int dx = -radius; dx <= radius; dx++) {
×
743
            // Calculate current point to check
744
            int nx = x_int + dx;
×
745
            int ny = y_int + dy;
×
746

747
            if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
×
UNCOV
748
                continue;
×
749
            }
750

751
            // Check if this is an edge pixel
752
            if (_current_edges.at<uchar>(ny, nx) > 0) {
×
753
                // Calculate distance squared (avoid square root for performance)
754
                float d_squared = dx * dx + dy * dy;
×
755

756
                // Update nearest edge if this is closer
757
                if (d_squared < min_distance) {
×
758
                    min_distance = d_squared;
×
759
                    nearest_edge = {static_cast<float>(nx), static_cast<float>(ny)};
×
760
                }
761
            }
762
        }
763
    }
764

765
    if (min_distance < radius * radius + 1) {
×
766
        std::cout << "Found edge point at (" << nearest_edge.first << ", "
×
767
                  << nearest_edge.second << "), distance: " << std::sqrt(min_distance) << std::endl;
×
768
    } else {
769
        std::cout << "No edge found within radius " << radius << std::endl;
×
UNCOV
770
        cv::imwrite("edges.png", _current_edges);
×
771
    }
772

773
    return nearest_edge;
×
774
}
775

776
void MediaLine_Widget::_setEraserRadius(int radius) {
×
777
    if (_selection_mode == Selection_Mode::Erase) {
×
778
        _scene->setHoverCircleRadius(static_cast<double>(radius));
×
779
    }
780
    std::cout << "Eraser radius set to: " << radius << std::endl;
×
UNCOV
781
}
×
782

UNCOV
783
void MediaLine_Widget::_toggleShowHoverCircle(bool checked) {
×
UNCOV
784
    _scene->setShowHoverCircle(checked);
×
UNCOV
785
    std::cout << "Show hover circle " << (checked ? "enabled" : "disabled") << std::endl;
×
786
}
×
787

788
void MediaLine_Widget::_setLineThickness(int thickness) {
×
789
    if (!_active_key.empty()) {
×
790
        auto line_opts = _scene->getLineConfig(_active_key);
×
791
        if (line_opts.has_value()) {
×
792
            line_opts.value()->line_thickness = thickness;
×
793
        }
794
        _scene->UpdateCanvas();
×
795
    }
796

797
    // Synchronize slider and spinbox if the signal came from one of them
798
    QObject * sender_obj = sender();
×
UNCOV
799
    if (sender_obj == ui->line_thickness_slider) {
×
800
        ui->line_thickness_spinbox->blockSignals(true);
×
801
        ui->line_thickness_spinbox->setValue(thickness);
×
802
        ui->line_thickness_spinbox->blockSignals(false);
×
803
    } else if (sender_obj == ui->line_thickness_spinbox) {
×
804
        ui->line_thickness_slider->blockSignals(true);
×
UNCOV
805
        ui->line_thickness_slider->setValue(thickness);
×
806
        ui->line_thickness_slider->blockSignals(false);
×
807
    }
808

809
    std::cout << "Line thickness set to: " << thickness << std::endl;
×
UNCOV
810
}
×
811

812
void MediaLine_Widget::_toggleShowPositionMarker(bool checked) {
×
813
    if (!_active_key.empty()) {
×
UNCOV
814
        auto line_opts = _scene->getLineConfig(_active_key);
×
815
        if (line_opts.has_value()) {
×
816
            line_opts.value()->show_position_marker = checked;
×
817
        }
UNCOV
818
        _scene->UpdateCanvas();
×
819
    }
820
    std::cout << "Show position marker " << (checked ? "enabled" : "disabled") << std::endl;
×
UNCOV
821
}
×
822

823
void MediaLine_Widget::_setPositionPercentage(int percentage) {
×
824
    if (!_active_key.empty()) {
×
825
        auto line_opts = _scene->getLineConfig(_active_key);
×
826
        if (line_opts.has_value()) {
×
827
            line_opts.value()->position_percentage = percentage;
×
828
        }
829
        _scene->UpdateCanvas();
×
830
    }
831

832
    // Synchronize slider and spinbox if the signal came from one of them
UNCOV
833
    QObject * sender_obj = sender();
×
UNCOV
834
    if (sender_obj == ui->position_percentage_slider) {
×
835
        ui->position_percentage_spinbox->blockSignals(true);
×
UNCOV
836
        ui->position_percentage_spinbox->setValue(percentage);
×
837
        ui->position_percentage_spinbox->blockSignals(false);
×
UNCOV
838
    } else if (sender_obj == ui->position_percentage_spinbox) {
×
UNCOV
839
        ui->position_percentage_slider->blockSignals(true);
×
UNCOV
840
        ui->position_percentage_slider->setValue(percentage);
×
841
        ui->position_percentage_slider->blockSignals(false);
×
842
    }
843

844
    std::cout << "Position percentage set to: " << percentage << std::endl;
×
845
}
×
846

847
void MediaLine_Widget::_toggleShowSegment(bool checked) {
×
848
    if (!_active_key.empty()) {
×
849
        auto line_opts = _scene->getLineConfig(_active_key);
×
UNCOV
850
        if (line_opts.has_value()) {
×
UNCOV
851
            line_opts.value()->show_segment = checked;
×
852
        }
853
        _scene->UpdateCanvas();
×
854
    }
UNCOV
855
    std::cout << "Show segment " << (checked ? "enabled" : "disabled") << std::endl;
×
856
}
×
857

858
void MediaLine_Widget::_setSegmentStartPercentage(int percentage) {
×
UNCOV
859
    if (_is_updating_percentages) return;
×
860
    _is_updating_percentages = true;
×
861

862
    if (!_active_key.empty()) {
×
UNCOV
863
        auto line_opts = _scene->getLineConfig(_active_key);
×
864
        if (line_opts.has_value()) {
×
865
            // Ensure start percentage doesn't exceed end percentage - 1%
UNCOV
866
            int max_start_percentage = line_opts.value()->segment_end_percentage - 1;
×
867
            if (percentage > max_start_percentage) {
×
868
                // Don't allow start to exceed end - 1%
869
                QObject * sender_obj = sender();
×
870
                if (sender_obj == ui->segment_start_slider) {
×
871
                    ui->segment_start_slider->blockSignals(true);
×
872
                    ui->segment_start_slider->setValue(max_start_percentage);
×
873
                    ui->segment_start_slider->blockSignals(false);
×
874
                } else if (sender_obj == ui->segment_start_spinbox) {
×
875
                    ui->segment_start_spinbox->blockSignals(true);
×
UNCOV
876
                    ui->segment_start_spinbox->setValue(max_start_percentage);
×
877
                    ui->segment_start_spinbox->blockSignals(false);
×
878
                }
UNCOV
879
                percentage = max_start_percentage;
×
880
            }
881

882
            line_opts.value()->segment_start_percentage = percentage;
×
883
        }
UNCOV
884
        _scene->UpdateCanvas();
×
885
    }
886

887
    // Synchronize the other control if this one was changed
888
    QObject * sender_obj = sender();
×
889
    if (sender_obj == ui->segment_start_slider) {
×
890
        ui->segment_start_spinbox->blockSignals(true);
×
891
        ui->segment_start_spinbox->setValue(percentage);
×
892
        ui->segment_start_spinbox->blockSignals(false);
×
893
    } else if (sender_obj == ui->segment_start_spinbox) {
×
894
        ui->segment_start_slider->blockSignals(true);
×
UNCOV
895
        ui->segment_start_slider->setValue(percentage);
×
UNCOV
896
        ui->segment_start_slider->blockSignals(false);
×
897
    }
898

UNCOV
899
    std::cout << "Segment start percentage set to: " << percentage << std::endl;
×
UNCOV
900
    _is_updating_percentages = false;
×
901
}
902

903
void MediaLine_Widget::_setSegmentEndPercentage(int percentage) {
×
904
    if (_is_updating_percentages) return;
×
905
    _is_updating_percentages = true;
×
906

UNCOV
907
    if (!_active_key.empty()) {
×
908
        auto line_opts = _scene->getLineConfig(_active_key);
×
909
        if (line_opts.has_value()) {
×
910
            // Ensure end percentage doesn't go below start percentage + 1%
UNCOV
911
            int min_end_percentage = line_opts.value()->segment_start_percentage + 1;
×
912
            if (percentage < min_end_percentage) {
×
913
                // Don't allow end to go below start + 1%
UNCOV
914
                QObject * sender_obj = sender();
×
915
                if (sender_obj == ui->segment_end_slider) {
×
916
                    ui->segment_end_slider->blockSignals(true);
×
UNCOV
917
                    ui->segment_end_slider->setValue(min_end_percentage);
×
UNCOV
918
                    ui->segment_end_slider->blockSignals(false);
×
UNCOV
919
                } else if (sender_obj == ui->segment_end_spinbox) {
×
UNCOV
920
                    ui->segment_end_spinbox->blockSignals(true);
×
UNCOV
921
                    ui->segment_end_spinbox->setValue(min_end_percentage);
×
UNCOV
922
                    ui->segment_end_spinbox->blockSignals(false);
×
923
                }
UNCOV
924
                percentage = min_end_percentage;
×
925
            }
926

927
            line_opts.value()->segment_end_percentage = percentage;
×
928
        }
UNCOV
929
        _scene->UpdateCanvas();
×
930
    }
931

932
    // Synchronize the other control if this one was changed
UNCOV
933
    QObject * sender_obj = sender();
×
934
    if (sender_obj == ui->segment_end_slider) {
×
935
        ui->segment_end_spinbox->blockSignals(true);
×
UNCOV
936
        ui->segment_end_spinbox->setValue(percentage);
×
UNCOV
937
        ui->segment_end_spinbox->blockSignals(false);
×
UNCOV
938
    } else if (sender_obj == ui->segment_end_spinbox) {
×
939
        ui->segment_end_slider->blockSignals(true);
×
UNCOV
940
        ui->segment_end_slider->setValue(percentage);
×
UNCOV
941
        ui->segment_end_slider->blockSignals(false);
×
942
    }
943

UNCOV
944
    std::cout << "Segment end percentage set to: " << percentage << std::endl;
×
945
    _is_updating_percentages = false;
×
946
}
947

UNCOV
948
void MediaLine_Widget::_rightClickedInVideo(qreal x_canvas, qreal y_canvas) {
×
949
    // Only handle right-clicks in Select mode and when a line is selected
950
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
UNCOV
951
    if (_selection_mode != Selection_Mode::Select || selected_line_index < 0 || _active_key.empty()) {
×
UNCOV
952
        return;
×
953
    }
954

955
    auto x_media = static_cast<float>(x_canvas);
×
UNCOV
956
    auto y_media = static_cast<float>(y_canvas);
×
957

958
    // Check if the right-click is near the selected line
959
    int nearest_line = _findNearestLine(x_media, y_media);
×
960
    if (nearest_line == selected_line_index) {
×
961
        // Show context menu at the click position
UNCOV
962
        QPoint global_pos = QCursor::pos();
×
963
        _showLineContextMenu(global_pos);
×
964
    }
965
}
966

967
/**
968
 * @brief Calculate the minimum distance from a point to a line segment
969
 * @param point The point to measure distance from
970
 * @param line_start Start point of the line segment
971
 * @param line_end End point of the line segment
972
 * @return The minimum distance from the point to the line segment
973
 */
974
float MediaLine_Widget::_calculateDistanceToLineSegment(Point2D<float> const & point,
×
975
                                                        Point2D<float> const & line_start,
976
                                                        Point2D<float> const & line_end) {
977
    float dx = line_end.x - line_start.x;
×
978
    float dy = line_end.y - line_start.y;
×
979

980
    // Handle degenerate case where line segment is actually a point
UNCOV
981
    if (dx == 0.0f && dy == 0.0f) {
×
982
        return calc_distance(point, line_start);
×
983
    }
984

985
    // Calculate the parameter t for the closest point on the line segment
986
    float t = ((point.x - line_start.x) * dx + (point.y - line_start.y) * dy) / (dx * dx + dy * dy);
×
987

988
    // Clamp t to [0, 1] to stay within the line segment
UNCOV
989
    t = std::max(0.0f, std::min(1.0f, t));
×
990

991
    // Calculate the closest point on the line segment
992
    Point2D<float> closest_point = {
×
993
            line_start.x + t * dx,
×
994
            line_start.y + t * dy};
×
995

996
    // Return the distance from the point to the closest point on the line segment
UNCOV
997
    return calc_distance(point, closest_point);
×
998
}
999

UNCOV
1000
int MediaLine_Widget::_findNearestLine(float x, float y) {
×
1001
    if (_active_key.empty()) {
×
1002
        return -1;
×
1003
    }
1004

UNCOV
1005
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
UNCOV
1006
    if (!line_data) {
×
UNCOV
1007
        return -1;
×
1008
    }
1009

UNCOV
1010
    auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
1011
    auto lines = line_data->getAtTime(current_time);
×
1012

UNCOV
1013
    if (lines.empty()) {
×
UNCOV
1014
        return -1;
×
1015
    }
1016

1017
    Point2D<float> click_point{x, y};
×
1018
    int nearest_line_index = -1;
×
UNCOV
1019
    float min_distance = _line_selection_threshold + 1;// Initialize beyond threshold
×
1020

UNCOV
1021
    for (int line_idx = 0; line_idx < static_cast<int>(lines.size()); ++line_idx) {
×
1022
        auto const & line = lines[line_idx];
×
1023

1024
        if (line.empty()) {
×
1025
            continue;
×
1026
        }
1027

1028
        // Calculate distance to each line segment
1029
        float min_segment_distance = std::numeric_limits<float>::max();
×
1030
        for (size_t i = 0; i < line.size() - 1; ++i) {
×
1031
            float segment_distance = _calculateDistanceToLineSegment(click_point, line[i], line[i + 1]);
×
UNCOV
1032
            if (segment_distance < min_segment_distance) {
×
1033
                min_segment_distance = segment_distance;
×
1034
            }
1035
        }
1036

1037
        // Also check distance to vertices for completeness
1038
        float min_vertex_distance = std::numeric_limits<float>::max();
×
UNCOV
1039
        for (auto const & vertex: line) {
×
UNCOV
1040
            float vertex_distance = calc_distance(click_point, vertex);
×
1041
            if (vertex_distance < min_vertex_distance) {
×
1042
                min_vertex_distance = vertex_distance;
×
1043
            }
1044
        }
1045

UNCOV
1046
        float line_distance = std::min(min_vertex_distance, min_segment_distance);
×
1047

1048
        if (line_distance < min_distance) {
×
UNCOV
1049
            min_distance = line_distance;
×
1050
            nearest_line_index = line_idx;
×
1051
            std::cout << "  -> New closest line!" << std::endl;
×
1052
        }
1053
    }
1054

UNCOV
1055
    return (min_distance <= _line_selection_threshold) ? nearest_line_index : -1;
×
1056
}
×
1057

1058
void MediaLine_Widget::_selectLine(int line_index) {
×
1059
    _selected_line_index = line_index;
×
1060

1061
    // Update the line display options to show the selected line differently
UNCOV
1062
    if (!_active_key.empty()) {
×
UNCOV
1063
        auto line_opts = _scene->getLineConfig(_active_key);
×
1064
        if (line_opts.has_value()) {
×
1065
            line_opts.value()->selected_line_index = line_index;
×
1066
        }
UNCOV
1067
        _scene->UpdateCanvas();
×
1068
    }
1069
}
×
1070

UNCOV
1071
void MediaLine_Widget::_clearLineSelection() {
×
1072
    _selected_line_index = -1;
×
1073

1074
    // Update the line display options to clear selection
UNCOV
1075
    if (!_active_key.empty()) {
×
1076
        auto line_opts = _scene->getLineConfig(_active_key);
×
1077
        if (line_opts.has_value()) {
×
1078
            line_opts.value()->selected_line_index = -1;
×
1079
        }
UNCOV
1080
        _scene->UpdateCanvas();
×
1081
    }
1082
}
×
1083

UNCOV
1084
void MediaLine_Widget::_showLineContextMenu(QPoint const & position) {
×
1085
    QMenu context_menu(this);
×
1086

1087
    // Create Move To submenu
UNCOV
1088
    QMenu * move_menu = context_menu.addMenu("Move Line To");
×
UNCOV
1089
    QMenu * copy_menu = context_menu.addMenu("Copy Line To");
×
1090

1091
    // Get available LineData keys
UNCOV
1092
    auto available_keys = _getAvailableLineDataKeys();
×
1093

1094
    for (auto const & key: available_keys) {
×
1095
        if (key != _active_key) {// Don't include the current key
×
1096
            // Add to Move menu
UNCOV
1097
            QAction * move_action = move_menu->addAction(QString::fromStdString(key));
×
UNCOV
1098
            connect(move_action, &QAction::triggered, [this, key]() {
×
1099
                _moveLineToTarget(key);
×
UNCOV
1100
            });
×
1101

1102
            // Add to Copy menu
UNCOV
1103
            QAction * copy_action = copy_menu->addAction(QString::fromStdString(key));
×
UNCOV
1104
            connect(copy_action, &QAction::triggered, [this, key]() {
×
1105
                _copyLineToTarget(key);
×
1106
            });
×
1107
        }
1108
    }
1109

1110
    // Disable menus if no other LineData available
UNCOV
1111
    if (available_keys.size() <= 1) {
×
UNCOV
1112
        move_menu->setEnabled(false);
×
1113
        copy_menu->setEnabled(false);
×
1114
    }
1115

UNCOV
1116
    context_menu.exec(position);
×
UNCOV
1117
}
×
1118

1119
std::vector<std::string> MediaLine_Widget::_getAvailableLineDataKeys() {
×
UNCOV
1120
    return _data_manager->getKeys<LineData>();
×
1121
}
1122

UNCOV
1123
void MediaLine_Widget::_moveLineToTarget(std::string const & target_key) {
×
1124
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
1125
    if (selected_line_index < 0 || _active_key.empty()) {
×
1126
        return;
×
1127
    }
1128

UNCOV
1129
    auto source_line_data = _data_manager->getData<LineData>(_active_key);
×
1130
    auto target_line_data = _data_manager->getData<LineData>(target_key);
×
1131

UNCOV
1132
    if (!source_line_data || !target_line_data) {
×
1133
        std::cerr << "Could not retrieve source or target LineData" << std::endl;
×
1134
        return;
×
1135
    }
1136

UNCOV
1137
    auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
1138
    auto lines = source_line_data->getAtTime(current_time);
×
1139

UNCOV
1140
    if (selected_line_index >= static_cast<int>(lines.size())) {
×
1141
        std::cerr << "Selected line index out of bounds" << std::endl;
×
1142
        return;
×
1143
    }
1144

1145
    // Get the selected line
UNCOV
1146
    Line2D selected_line = lines[selected_line_index];
×
1147

1148
    // Add to target
UNCOV
1149
    target_line_data->addAtTime(current_time, selected_line);
×
1150

1151
    // Remove from source by rebuilding the vector without the selected line
UNCOV
1152
    std::vector<Line2D> remaining_lines;
×
1153
    for (int i = 0; i < static_cast<int>(lines.size()); ++i) {
×
1154
        if (i != selected_line_index) {
×
UNCOV
1155
            remaining_lines.push_back(lines[i]);
×
1156
        }
1157
    }
1158

1159
    // Clear and rebuild source lines
1160
    source_line_data->clearAtTime(TimeFrameIndex(current_time));
×
UNCOV
1161
    for (auto const & line: remaining_lines) {
×
1162
        source_line_data->addAtTime(current_time, line);
×
1163
    }
1164

1165
    // Clear selection since the line was moved
1166
    _scene->clearAllSelections();
×
1167

1168
    std::cout << "Moved line from " << _active_key << " to " << target_key << std::endl;
×
UNCOV
1169
}
×
1170

1171
void MediaLine_Widget::_copyLineToTarget(std::string const & target_key) {
×
1172
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
1173
    if (selected_line_index < 0 || _active_key.empty()) {
×
1174
        return;
×
1175
    }
1176

1177
    auto source_line_data = _data_manager->getData<LineData>(_active_key);
×
1178
    auto target_line_data = _data_manager->getData<LineData>(target_key);
×
1179

1180
    if (!source_line_data || !target_line_data) {
×
UNCOV
1181
        std::cerr << "Could not retrieve source or target LineData" << std::endl;
×
UNCOV
1182
        return;
×
1183
    }
1184

1185
    auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
1186
    auto lines = source_line_data->getAtTime(current_time);
×
1187

UNCOV
1188
    if (selected_line_index >= static_cast<int>(lines.size())) {
×
UNCOV
1189
        std::cerr << "Selected line index out of bounds" << std::endl;
×
UNCOV
1190
        return;
×
1191
    }
1192

1193
    // Get the selected line and copy it to target
1194
    Line2D selected_line = lines[selected_line_index];
×
1195
    target_line_data->addAtTime(current_time, selected_line);
×
1196

UNCOV
1197
    std::cout << "Copied line from " << _active_key << " to " << target_key << std::endl;
×
1198
}
×
1199

1200
void MediaLine_Widget::_addPointToDrawAllFrames(float x_media, float y_media) {
×
1201
    if (_drawAllFramesSelectionWidget && _drawAllFramesSelectionWidget->isDrawingActive()) {
×
1202
        // Store the points in default media coordinates for the temporary line
UNCOV
1203
        _drawAllFramesSelectionWidget->addPoint(Point2D<float>{x_media, y_media});
×
1204
        
1205
        // Update the temporary line visualization using default aspect ratios
1206
        auto current_points = _drawAllFramesSelectionWidget->getCurrentLinePoints();
×
1207
        _scene->updateTemporaryLine(current_points, ""); // Use default aspect ratios
×
1208
        
1209
    }
×
1210
}
×
1211

UNCOV
1212
void MediaLine_Widget::_applyLineToAllFrames() {
×
1213
    if (!_drawAllFramesSelectionWidget || _active_key.empty()) {
×
UNCOV
1214
        std::cout << "Cannot apply line to all frames: widget not available or no active key" << std::endl;
×
UNCOV
1215
        return;
×
1216
    }
1217

1218
    auto line_points = _drawAllFramesSelectionWidget->getCurrentLinePoints();
×
1219
    if (line_points.empty()) {
×
UNCOV
1220
        std::cout << "No line points to apply to all frames" << std::endl;
×
UNCOV
1221
        return;
×
1222
    }
1223

1224
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
1225
    if (!line_data) {
×
1226
        std::cout << "No line data available for active key" << std::endl;
×
UNCOV
1227
        return;
×
1228
    }
1229

1230
    // Get all frame times
UNCOV
1231
    auto frame_times = _getAllFrameTimes();
×
UNCOV
1232
    if (frame_times.empty()) {
×
1233
        std::cout << "No frame times available" << std::endl;
×
UNCOV
1234
        return;
×
1235
    }
1236

1237
    // Convert coordinates from default media to line-specific coordinates if needed
1238
    Line2D line_to_apply;
×
1239
    if (!_active_key.empty()) {
×
UNCOV
1240
        auto line_data_for_conversion = _data_manager->getData<LineData>(_active_key);
×
1241
        if (line_data_for_conversion) {
×
1242
            auto image_size = line_data_for_conversion->getImageSize();
×
1243
            
1244
            // If the line data has specific image dimensions, convert coordinates
1245
            if (image_size.width != -1 && image_size.height != -1) {
×
1246
                float default_xAspect = _scene->getXAspect();
×
1247
                float default_yAspect = _scene->getYAspect();
×
1248
                float line_xAspect = static_cast<float>(_scene->getCanvasSize().first) / image_size.width;
×
UNCOV
1249
                float line_yAspect = static_cast<float>(_scene->getCanvasSize().second) / image_size.height;
×
1250
                
1251
                // Convert each point from default media coordinates to line-specific coordinates
1252
                for (auto const & point : line_points) {
×
1253
                    float x_canvas = point.x * default_xAspect;
×
1254
                    float y_canvas = point.y * default_yAspect;
×
UNCOV
1255
                    float x_converted = x_canvas / line_xAspect;
×
UNCOV
1256
                    float y_converted = y_canvas / line_yAspect;
×
UNCOV
1257
                    line_to_apply.push_back(Point2D<float>{x_converted, y_converted});
×
1258
                }
1259
            } else {
1260
                line_to_apply = Line2D(line_points);
×
1261
            }
1262
        } else {
UNCOV
1263
            line_to_apply = Line2D(line_points);
×
1264
        }
UNCOV
1265
    } else {
×
1266
        line_to_apply = Line2D(line_points);
×
1267
    }
1268

1269
    // Apply the line to all frames
UNCOV
1270
    int frames_processed = 0;
×
1271
    for (auto const & frame_time: frame_times) {
×
1272
        line_data->addAtTime(frame_time, line_to_apply, false);// Don't notify for each frame
×
UNCOV
1273
        frames_processed++;
×
1274
    }
1275

1276
    // Notify observers once at the end
1277
    line_data->notifyObservers();
×
1278

1279
    // Clear the line points after applying
UNCOV
1280
    _drawAllFramesSelectionWidget->clearLinePoints();
×
1281
    
1282
    // Clear the temporary line visualization
1283
    _scene->clearTemporaryLine();
×
1284

1285
    _scene->UpdateCanvas();
×
1286
}
×
1287

UNCOV
1288
std::vector<TimeFrameIndex> MediaLine_Widget::_getAllFrameTimes() {
×
UNCOV
1289
    std::vector<TimeFrameIndex> frame_times;
×
1290

1291
    // Get media data to determine total frame count
UNCOV
1292
    auto media_data = _data_manager->getData<MediaData>("media");
×
UNCOV
1293
    if (!media_data) {
×
UNCOV
1294
        std::cout << "No media data available" << std::endl;
×
UNCOV
1295
        return frame_times;
×
1296
    }
1297

UNCOV
1298
    int total_frames = media_data->getTotalFrameCount();
×
UNCOV
1299
    if (total_frames <= 0) {
×
UNCOV
1300
        std::cout << "Invalid frame count: " << total_frames << std::endl;
×
UNCOV
1301
        return frame_times;
×
1302
    }
1303

1304
    // Create TimeFrameIndex for each frame
UNCOV
1305
    frame_times.reserve(total_frames);
×
UNCOV
1306
    for (int i = 0; i < total_frames; ++i) {
×
UNCOV
1307
        frame_times.emplace_back(i);
×
1308
    }
1309

1310
    return frame_times;
UNCOV
1311
}
×
1312

UNCOV
1313
int MediaLine_Widget::_getSelectedLineIndexFromGroupSystem() const {
×
1314
    // Get the selected entities from the group system
UNCOV
1315
    auto selected_entities = _scene->getSelectedEntities();
×
1316
    
UNCOV
1317
    std::cout << "Debug: Selected entities count: " << selected_entities.size() << std::endl;
×
1318
    
1319
    // If no entities are selected, return -1
UNCOV
1320
    if (selected_entities.empty()) {
×
UNCOV
1321
        std::cout << "Debug: No entities selected" << std::endl;
×
UNCOV
1322
        return -1;
×
1323
    }
1324
    
1325
    // For now, just return the first selected entity as the line index
1326
    // EntityId for lines corresponds to their index in the line data
UNCOV
1327
    EntityId first_selected = *selected_entities.begin();
×
UNCOV
1328
    std::cout << "Debug: First selected entity ID: " << first_selected << std::endl;
×
UNCOV
1329
    return static_cast<int>(first_selected);
×
UNCOV
1330
}
×
1331

UNCOV
1332
void MediaLine_Widget::_updateTemporaryLineFromWidget() {
×
UNCOV
1333
    if (_drawAllFramesSelectionWidget) {
×
UNCOV
1334
        auto current_points = _drawAllFramesSelectionWidget->getCurrentLinePoints();
×
UNCOV
1335
        _scene->updateTemporaryLine(current_points, ""); // Use default aspect ratios
×
UNCOV
1336
    }
×
UNCOV
1337
}
×
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