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

paulmthompson / WhiskerToolbox / 18018707227

25 Sep 2025 07:35PM UTC coverage: 68.577% (-0.3%) from 68.889%
18018707227

push

github

paulmthompson
selection for lines appears to work well

11 of 59 new or added lines in 2 files covered. (18.64%)

797 existing lines in 9 files now uncovered.

42024 of 61280 relevant lines covered (68.58%)

1133.34 hits per line

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

14.5
/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["Add Points"] = Selection_Mode::Add;
3✔
41
    _selection_modes["Erase Points"] = Selection_Mode::Erase;
3✔
42
    _selection_modes["Select Line"] = Selection_Mode::Select;
3✔
43
    _selection_modes["Draw Across All Frames"] = Selection_Mode::DrawAllFrames;
3✔
44

45
    ui->selection_mode_combo->addItems(QStringList(_selection_modes.keys()));
3✔
46

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

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

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

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

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

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

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

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

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

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

100
    connect(ui->line_select_slider, &QSlider::valueChanged, this, &MediaLine_Widget::_lineSelectionChanged);
3✔
101

102
    _setupSelectionModePages();
3✔
103
}
3✔
104

105
void MediaLine_Widget::_setupSelectionModePages() {
3✔
106
    _noneSelectionWidget = new line_widget::LineNoneSelectionWidget();
3✔
107
    ui->mode_stacked_widget->addWidget(_noneSelectionWidget);
3✔
108

109
    _addSelectionWidget = new line_widget::LineAddSelectionWidget();
3✔
110
    ui->mode_stacked_widget->addWidget(_addSelectionWidget);
3✔
111

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

123
    _eraseSelectionWidget = new line_widget::LineEraseSelectionWidget();
3✔
124
    ui->mode_stacked_widget->addWidget(_eraseSelectionWidget);
3✔
125

126
    connect(_eraseSelectionWidget, &line_widget::LineEraseSelectionWidget::eraserRadiusChanged,
9✔
127
            this, &MediaLine_Widget::_setEraserRadius);
6✔
128
    connect(_eraseSelectionWidget, &line_widget::LineEraseSelectionWidget::showCircleToggled,
9✔
129
            this, &MediaLine_Widget::_toggleShowHoverCircle);
6✔
130

131
    _selectSelectionWidget = new line_widget::LineSelectSelectionWidget();
3✔
132
    ui->mode_stacked_widget->addWidget(_selectSelectionWidget);
3✔
133

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

140
    _drawAllFramesSelectionWidget = new line_widget::LineDrawAllFramesSelectionWidget();
3✔
141
    ui->mode_stacked_widget->addWidget(_drawAllFramesSelectionWidget);
3✔
142

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

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

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

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

163
    static_cast<void>(event);
164

165
    std::cout << "Show Event" << std::endl;
×
166
    connect(_scene, &Media_Window::leftClickMedia, this, &MediaLine_Widget::_clickedInVideo);
×
167
    connect(_scene, &Media_Window::rightClickMedia, this, &MediaLine_Widget::_rightClickedInVideo);
×
168
}
×
169

170
void MediaLine_Widget::hideEvent(QHideEvent * event) {
3✔
171

172
    static_cast<void>(event);
173

174
    std::cout << "Hide Event" << std::endl;
3✔
175
    disconnect(_scene, &Media_Window::leftClickMedia, this, &MediaLine_Widget::_clickedInVideo);
3✔
176
    disconnect(_scene, &Media_Window::rightClickMedia, this, &MediaLine_Widget::_rightClickedInVideo);
3✔
177

178
    // Clean up hover circle when switching away from line widget
179
    _scene->setShowHoverCircle(false);
3✔
180
}
3✔
181

182
void MediaLine_Widget::setActiveKey(std::string const & key) {
×
183
    _active_key = key;
×
184
    ui->name_label->setText(QString::fromStdString(key));
×
185

186
    // Set the color picker to the current line color if available
187
    if (!key.empty()) {
×
188
        auto config = _scene->getLineConfig(key);
×
189

190
        if (config) {
×
191
            ui->color_picker->setColor(QString::fromStdString(config.value()->hex_color));
×
192
            ui->color_picker->setAlpha(static_cast<int>(config.value()->alpha * 100));
×
193

194
            // Set line thickness controls
195
            ui->line_thickness_slider->blockSignals(true);
×
196
            ui->line_thickness_spinbox->blockSignals(true);
×
197
            ui->line_thickness_slider->setValue(config.value()->line_thickness);
×
198
            ui->line_thickness_spinbox->setValue(config.value()->line_thickness);
×
199
            ui->line_thickness_slider->blockSignals(false);
×
200
            ui->line_thickness_spinbox->blockSignals(false);
×
201

202
            // Update the show points checkbox directly from the UI file
203
            ui->show_points_checkbox->blockSignals(true);
×
204
            ui->show_points_checkbox->setChecked(config.value()->show_points);
×
205
            ui->show_points_checkbox->blockSignals(false);
×
206

207
            // Set position marker controls
208
            ui->show_position_marker_checkbox->blockSignals(true);
×
209
            ui->show_position_marker_checkbox->setChecked(config.value()->show_position_marker);
×
210
            ui->show_position_marker_checkbox->blockSignals(false);
×
211

212
            ui->position_percentage_slider->blockSignals(true);
×
213
            ui->position_percentage_spinbox->blockSignals(true);
×
214
            ui->position_percentage_slider->setValue(config.value()->position_percentage);
×
215
            ui->position_percentage_spinbox->setValue(config.value()->position_percentage);
×
216
            ui->position_percentage_slider->blockSignals(false);
×
217
            ui->position_percentage_spinbox->blockSignals(false);
×
218

219
            // Set segment controls
220
            ui->show_segment_checkbox->blockSignals(true);
×
221
            ui->show_segment_checkbox->setChecked(config.value()->show_segment);
×
222
            ui->show_segment_checkbox->blockSignals(false);
×
223

224
            ui->segment_start_slider->blockSignals(true);
×
225
            ui->segment_start_spinbox->blockSignals(true);
×
226
            ui->segment_start_slider->setValue(config.value()->segment_start_percentage);
×
227
            ui->segment_start_spinbox->setValue(config.value()->segment_start_percentage);
×
228
            ui->segment_start_slider->blockSignals(false);
×
229
            ui->segment_start_spinbox->blockSignals(false);
×
230

231
            ui->segment_end_slider->blockSignals(true);
×
232
            ui->segment_end_spinbox->blockSignals(true);
×
233
            ui->segment_end_slider->setValue(config.value()->segment_end_percentage);
×
234
            ui->segment_end_spinbox->setValue(config.value()->segment_end_percentage);
×
235
            ui->segment_end_slider->blockSignals(false);
×
236
            ui->segment_end_spinbox->blockSignals(false);
×
237

238
            // Reset line selection and update slider
239
            _current_line_index = 0;
×
240

241
            auto line_data = _data_manager->getData<LineData>(_active_key);
×
242
            if (line_data) {
×
243
                auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
244
                auto lines = line_data->getAtTime(current_time);
×
245

246
                // Update the slider with the number of lines
247
                int num_lines = static_cast<int>(lines.size());
×
248

249
                ui->line_select_slider->blockSignals(true);
×
250
                ui->line_select_slider->setMaximum(num_lines > 0 ? num_lines - 1 : 0);
×
251
                ui->line_select_slider->setValue(0);// Reset to first line
×
252
                ui->line_select_slider->setEnabled(num_lines > 1);
×
253
                ui->line_select_slider->blockSignals(false);
×
254
            }
×
255
        }
×
256
    }
257
}
×
258

259
void MediaLine_Widget::_setLineAlpha(int alpha) {
×
260
    float const alpha_float = static_cast<float>(alpha) / 100;
×
261

262
    if (!_active_key.empty()) {
×
263
        auto line_opts = _scene->getLineConfig(_active_key);
×
264
        if (line_opts.has_value()) {
×
265
            line_opts.value()->alpha = alpha_float;
×
266
        }
267
        _scene->UpdateCanvas();
×
268
    }
269
}
×
270

271
void MediaLine_Widget::_setLineColor(QString const & hex_color) {
×
272
    if (!_active_key.empty()) {
×
273
        auto line_opts = _scene->getLineConfig(_active_key);
×
274
        if (line_opts.has_value()) {
×
275
            line_opts.value()->hex_color = hex_color.toStdString();
×
276
        }
277
        _scene->UpdateCanvas();
×
278
    }
279
}
×
280

281
void MediaLine_Widget::_clickedInVideo(qreal x_canvas, qreal y_canvas) {
×
282
    if (_active_key.empty()) {
×
283
        std::cout << "No active key" << std::endl;
×
284
        return;
×
285
    }
286

287
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
288
    if (!line_data) {
×
289
        std::cout << "No line data for active key" << std::endl;
×
290
        return;
×
291
    }
292

293
    auto const x_media = static_cast<float>(x_canvas);
×
294
    auto const y_media = static_cast<float>(y_canvas);
×
295
    auto const current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
296
    auto const line_img_size = line_data->getImageSize();
×
297

298
    auto lines = line_data->getAtTime(current_time);
×
299

300
    switch (_selection_mode) {
×
301
        case Selection_Mode::None: {
×
302
            std::cout << "Selection mode is None" << std::endl;
×
303
            break;
×
304
        }
305
        case Selection_Mode::Add: {
×
306
            std::cout << "Selection mode is Add" << std::endl;
×
307
            _addPointToLine(x_media, y_media, current_time);
×
308
            break;
×
309
        }
310
        case Selection_Mode::Erase: {
×
311
            std::cout << "Selection mode is Erase" << std::endl;
×
312
            std::cout << "Not yet implemented" << std::endl;
×
313
            break;
×
314
        }
315
        case Selection_Mode::Select: {
×
316
            std::cout << "Selection mode is Select" << std::endl;
×
317
            
318
            // Use Media_Window's entity finding and selection system for group integration
NEW
319
            QPointF scene_pos(x_canvas * _scene->getXAspect(), y_canvas * _scene->getYAspect());
×
NEW
320
            std::string data_key, data_type;
×
NEW
321
            EntityId entity_id = _scene->findEntityAtPosition(scene_pos, data_key, data_type);
×
322
            
NEW
323
            if (entity_id != 0 && data_type == "line" && data_key == _active_key) {
×
324
                // Use the group-based selection system for consistency
NEW
325
                _scene->selectEntity(entity_id, data_key, data_type);
×
NEW
326
                std::cout << "Selected line entity " << entity_id << " in group system" << std::endl;
×
327
            } else {
328
                // Clear selections if no line found
NEW
329
                _scene->clearAllSelections();
×
NEW
330
                std::cout << "No line found within threshold - cleared selections" << std::endl;
×
331
            }
332
            break;
×
UNCOV
333
        }
×
334
        case Selection_Mode::DrawAllFrames: {
×
335
            std::cout << "Selection mode is DrawAllFrames" << std::endl;
×
336
            _addPointToDrawAllFrames(x_media, y_media);
×
337
            break;
×
338
        }
339
    }
340
}
×
341

342
void MediaLine_Widget::_addPointToLine(float x_media, float y_media, TimeFrameIndex current_time) {
×
343
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
344
    auto lines = line_data->getAtTime(current_time);
×
345

346
    // Check if edge snapping is enabled
347
    bool use_edge_snapping = false;
×
348
    auto line_opts = _scene->getLineConfig(_active_key);
×
349
    if (line_opts.has_value()) {
×
350
        use_edge_snapping = line_opts.value()->edge_snapping;
×
351
    }
352

353
    if (use_edge_snapping && _edge_snapping_enabled) {
×
354

355
        if (_current_edges.empty()) {
×
356
            _detectEdges();
×
357
        }
358

359
        auto edge_point = _findNearestEdge(x_media, y_media);
×
360
        x_media = edge_point.first;
×
361
        y_media = edge_point.second;
×
362
    }
363

364
    if (lines.empty()) {
×
365
        // If no lines exist, create a new one with the single point
366
        _data_manager->getData<LineData>(_active_key)->addAtTime(current_time, Line2D{Point2D{x_media, y_media}});
×
367
        // After adding a new line, it's line index 0
368
        _current_line_index = 0;
×
369
        ui->line_select_slider->setValue(0);
×
370
    } else {
371
        if (_smoothing_mode == Smoothing_Mode::SimpleSmooth) {
×
372
            // Use the original smoothing approach with the selected line index
373
            _data_manager->getData<LineData>(_active_key)->addPointToLineInterpolate(current_time, _current_line_index, Point2D<float>{x_media, y_media});
×
374
        } else if (_smoothing_mode == Smoothing_Mode::PolynomialFit) {
×
375
            // Make sure current_line_index is valid
376
            if (_current_line_index >= static_cast<int>(lines.size())) {
×
377
                std::cout << "Warning: line index out of bounds, using first line" << std::endl;
×
378
                _current_line_index = 0;
×
379
                ui->line_select_slider->setValue(0);
×
380
            }
381

382
            // Get a copy of the current line using the selected index
383
            auto line = lines[_current_line_index];
×
384

385
            // If the line already exists, add interpolated points between the last point and the new point
386
            if (!line.empty()) {
×
387
                Point2D<float> const last_point = line.back();
×
388

389
                // Calculate distance between last point and new point
390
                float dx = x_media - last_point.x;
×
391
                float dy = y_media - last_point.y;
×
392
                float distance = std::sqrt(dx * dx + dy * dy);
×
393

394
                // Add interpolated points if the distance is significant
395
                if (distance > 5.0f) {// Threshold for adding interpolation
×
396
                    int num_interp_points = std::max(2, static_cast<int>(distance / 5.0f));
×
397
                    for (int i = 1; i <= num_interp_points; ++i) {
×
398
                        float t = static_cast<float>(i) / (num_interp_points + 1);
×
399
                        float interp_x = last_point.x + t * dx;
×
400
                        float interp_y = last_point.y + t * dy;
×
401
                        line.push_back(Point2D<float>{interp_x, interp_y});
×
402
                    }
403
                }
404
            }
405

406
            // Add the actual new point
407
            line.push_back(Point2D<float>{x_media, y_media});
×
408

409
            // Apply polynomial fitting if we have enough points
410
            if (line.size() >= 3) {
×
411
                _applyPolynomialFit(line, _polynomial_order);
×
412
            }
413

414
            // Update the line in the data manager
415
            std::vector<Line2D> updated_lines = lines;
×
416
            updated_lines[_current_line_index] = line;
×
417

418
            _data_manager->getData<LineData>(_active_key)->clearAtTime(current_time);
×
419
            for (auto const & updated_line: updated_lines) {
×
420
                _data_manager->getData<LineData>(_active_key)->addAtTime(current_time, updated_line);
×
421
            }
422
        }
×
423
    }
424

425
    _scene->UpdateCanvas();
×
426
    std::cout << "Added point (" << x_media << ", " << y_media << ") to line "
×
427
              << _active_key << " (index: " << _current_line_index << ")" << std::endl;
×
428
}
×
429

430
void MediaLine_Widget::_applyPolynomialFit(Line2D & line, int order) {
×
431
    if (line.size() < static_cast<size_t>(order + 1)) {
×
432
        // Not enough points for the requested polynomial order
433
        return;
×
434
    }
435

436
    // Extract x and y coordinates
437
    std::vector<double> t(line.size());
×
438
    std::vector<double> x_coords(line.size());
×
439
    std::vector<double> y_coords(line.size());
×
440

441
    // Use parameter t along the curve (0 to 1)
442
    for (size_t i = 0; i < line.size(); ++i) {
×
443
        t[i] = static_cast<double>(i) / (line.size() - 1);
×
444
        x_coords[i] = line[i].x;
×
445
        y_coords[i] = line[i].y;
×
446
    }
447

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

452
    if (x_coeffs.empty() || y_coeffs.empty()) {
×
453
        // Fall back to simple smoothing if fitting failed
454
        smooth_line(line);
×
455
        return;
×
456
    }
457

458
    // Generate smooth curve with more points
459
    int const num_points = std::max(100, static_cast<int>(line.size()) * 2);
×
460
    std::vector<Point2D<float>> smooth_line;
×
461
    smooth_line.reserve(num_points);
×
462

463
    for (int i = 0; i < num_points; ++i) {
×
464
        double t_param = static_cast<double>(i) / (num_points - 1);
×
465

466
        // Evaluate polynomials at t_param using the function from line_angle.hpp
467
        double x_val = evaluate_polynomial(x_coeffs, t_param);
×
468
        double y_val = evaluate_polynomial(y_coeffs, t_param);
×
469

470
        smooth_line.push_back(Point2D<float>{static_cast<float>(x_val), static_cast<float>(y_val)});
×
471
    }
472

473
    // Replace the original line with the smooth one
474
    line = Line2D(smooth_line);
×
475
}
×
476

477
void MediaLine_Widget::_setSmoothingMode(int index) {
×
478
    _smoothing_mode = static_cast<Smoothing_Mode>(index);
×
479
    std::cout << "Smoothing mode set to: " << index << std::endl;
×
480
}
×
481

482
void MediaLine_Widget::_setPolynomialOrder(int order) {
×
483
    _polynomial_order = order;
×
484
    std::cout << "Polynomial order set to: " << order << std::endl;
×
485
}
×
486

487
void MediaLine_Widget::_toggleSelectionMode(QString text) {
3✔
488
    _selection_mode = _selection_modes[text];
3✔
489
    std::cout << "MediaLine_Widget: Selection mode changed to: " << text.toStdString() 
6✔
490
              << " (enum value: " << static_cast<int>(_selection_mode) << ")" << std::endl;
6✔
491

492
    // Switch to the appropriate page in the stacked widget
493
    int pageIndex = static_cast<int>(_selection_mode);
3✔
494
    ui->mode_stacked_widget->setCurrentIndex(pageIndex);
3✔
495

496
    // Enable group selection ONLY when in "Select Line" mode
497
    bool enable_group_selection = (_selection_mode == Selection_Mode::Select);
3✔
498
    _scene->setGroupSelectionEnabled(enable_group_selection);
3✔
499
    std::cout << "MediaLine_Widget: Group selection " << (enable_group_selection ? "enabled" : "disabled") << std::endl;
3✔
500

501
    if (_selection_mode == Selection_Mode::Erase) {
3✔
502
        _scene->setShowHoverCircle(true);
×
503
        _scene->setHoverCircleRadius(_eraseSelectionWidget->getEraserRadius());
×
504
    } else {
505
        _scene->setShowHoverCircle(false);
3✔
506
    }
507
}
3✔
508

509
void MediaLine_Widget::_toggleShowPoints(bool checked) {
×
510
    if (!_active_key.empty()) {
×
511
        auto line_opts = _scene->getLineConfig(_active_key);
×
512
        if (line_opts.has_value()) {
×
513
            line_opts.value()->show_points = checked;
×
514
        }
515
        _scene->UpdateCanvas();
×
516
    }
517
}
×
518

519
void MediaLine_Widget::_toggleEdgeSnapping(bool checked) {
×
520
    _edge_snapping_enabled = checked;
×
521

522
    if (!_active_key.empty()) {
×
523
        auto line_opts = _scene->getLineConfig(_active_key);
×
524
        if (line_opts.has_value()) {
×
525
            line_opts.value()->edge_snapping = checked;
×
526
        }
527

528
        // If enabling edge snapping, perform edge detection immediately
529
        if (checked) {
×
530
            _detectEdges();
×
531
        } else {
532
            // Clear cached edges when disabling
533
            _current_edges.release();
×
534
        }
535

536
        _scene->UpdateCanvas();
×
537
    }
538

539
    std::cout << "Edge snapping " << (checked ? "enabled" : "disabled") << std::endl;
×
540
}
×
541

542
void MediaLine_Widget::LoadFrame(int frame_id) {
×
543
    // Update the widget with the new frame
544
    // This could involve refreshing displays or updating UI elements
545
    // specific to the current frame
546

547
    // If we have an active line, we might want to update some UI
548
    // based on line data at this frame
549
    if (!_active_key.empty()) {
×
550
        auto line_data = _data_manager->getData<LineData>(_active_key);
×
551
        if (line_data) {
×
552
            auto lines = line_data->getAtTime(TimeFrameIndex(frame_id));
×
553

554
            // Update the line_select_slider's maximum value based on the number of lines
555
            int num_lines = static_cast<int>(lines.size());
×
556

557
            // Disconnect and reconnect to avoid triggering slider value changed signals
558
            // during this programmatic update
559
            ui->line_select_slider->blockSignals(true);
×
560

561
            // Set the maximum value to the number of lines - 1 (or 0 if there are no lines)
562
            // Slider indices are 0-based, so max should be (num_lines - 1) when there are lines
563
            ui->line_select_slider->setMaximum(num_lines > 0 ? num_lines - 1 : 0);
×
564

565
            // If the current slider value exceeds the new maximum, adjust it
566
            if (ui->line_select_slider->value() > ui->line_select_slider->maximum()) {
×
567
                ui->line_select_slider->setValue(ui->line_select_slider->maximum());
×
568
            }
569

570
            ui->line_select_slider->blockSignals(false);
×
571

572
            // Update slider enabled state
573
            ui->line_select_slider->setEnabled(num_lines > 1);
×
574

575
            std::cout << "Frame " << frame_id << ": Updated line selector for "
×
576
                      << num_lines << " lines in " << _active_key << std::endl;
×
577
        }
×
578
    }
×
579
}
×
580

581
void MediaLine_Widget::_lineSelectionChanged(int index) {
×
582
    if (_current_line_index == index) {
×
583
        return;// No change
×
584
    }
585

586
    _current_line_index = index;
×
587
    std::cout << "Selected line index: " << _current_line_index << std::endl;
×
588

589
    // Update any UI or visualization based on the selected line
590
    if (!_active_key.empty()) {
×
591
        auto line_data = _data_manager->getData<LineData>(_active_key);
×
592
        if (line_data) {
×
593
            auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
594
            auto lines = line_data->getAtTime(current_time);
×
595

596
            if (!lines.empty() && _current_line_index < static_cast<int>(lines.size())) {
×
597
                // Here you can perform any specific actions needed when a different line is selected
598
                // For example, updating a visualization to highlight the selected line
599

600
                // Request canvas update to reflect any visualization changes
601
                _scene->UpdateCanvas();
×
602
            }
603
        }
×
604
    }
×
605
}
606

607
void MediaLine_Widget::_setEdgeThreshold(int threshold) {
×
608
    _edge_threshold = threshold;
×
609
    std::cout << "Edge threshold set to: " << threshold << std::endl;
×
610
}
×
611

612
void MediaLine_Widget::_setEdgeSearchRadius(int radius) {
×
613
    _edge_search_radius = radius;
×
614
    std::cout << "Edge search radius set to: " << radius << std::endl;
×
615
}
×
616

617
void MediaLine_Widget::_detectEdges() {
×
618

619
    auto media = _data_manager->getData<MediaData>("media");
×
620
    if (!media) {
×
621
        std::cout << "No media data available for edge detection" << std::endl;
×
622
        return;
×
623
    }
624

625
    auto const current_time = _data_manager->getCurrentTime();
×
626
    auto frame_data = media->getProcessedData(current_time);
×
627

628
    // Convert raw frame data to cv::Mat
629
    /*
630
    int width = media->getWidth();
631
    int height = media->getHeight();
632
    
633
    // Create a grayscale image for edge detection
634
    cv::Mat gray_image;
635
    
636
    // Check the format of media data and create appropriate cv::Mat
637
    if (media->getFormat() == MediaData::DisplayFormat::Gray) {
638
        // Grayscale image
639
        gray_image = cv::Mat(height, width, CV_8UC1, frame_data.data());
640
    } else {
641
        // Color image (RGBA)
642
        _current_frame = cv::Mat(height, width, CV_8UC4, frame_data.data());
643
        
644
        // Convert to grayscale
645
        cv::cvtColor(_current_frame, gray_image, cv::COLOR_RGBA2GRAY);
646
    }
647
    */
648
    auto gray_image = ImageProcessing::convert_vector_to_mat(frame_data, media->getImageSize());
×
649

650
    //cv::Mat blurred;
651
    //cv::GaussianBlur(gray_image, blurred, cv::Size(5, 5), 1.5);
652
    //cv::Canny(blurred, _current_edges, _edge_threshold / 2, _edge_threshold);
653

654
    cv::Canny(gray_image, _current_edges, _edge_threshold / 2, _edge_threshold);
×
655

656
    std::cout << "Edge detection completed for frame " << current_time << std::endl;
×
657
    std::cout << "Edges detected: " << _current_edges.size() << std::endl;
×
658
}
×
659

660
std::pair<float, float> MediaLine_Widget::_findNearestEdge(float x, float y) {
×
661
    if (_current_edges.empty()) {
×
662
        return {x, y};
×
663
    }
664

665
    // Round x and y to integers (image coordinates)
666
    int x_int = static_cast<int>(std::round(x));
×
667
    int y_int = static_cast<int>(std::round(y));
×
668

669
    // Define search radius and initialize variables
670
    int radius = _edge_search_radius;
×
671
    float min_distance = radius * radius + 1;     // Initialize to something larger than possible
×
672
    std::pair<float, float> nearest_edge = {x, y};// Default to original point
×
673

674
    // Get image dimensions
675
    int width = _current_edges.cols;
×
676
    int height = _current_edges.rows;
×
677

678
    // Check if the point is within image bounds
679
    if (x_int < 0 || x_int >= width || y_int < 0 || y_int >= height) {
×
680
        std::cout << "Click point outside image bounds" << std::endl;
×
681
        return {x, y};
×
682
    }
683

684
    // Search in a square region around the clicked point
685
    for (int dy = -radius; dy <= radius; dy++) {
×
686
        for (int dx = -radius; dx <= radius; dx++) {
×
687
            // Calculate current point to check
688
            int nx = x_int + dx;
×
689
            int ny = y_int + dy;
×
690

691
            if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
×
692
                continue;
×
693
            }
694

695
            // Check if this is an edge pixel
696
            if (_current_edges.at<uchar>(ny, nx) > 0) {
×
697
                // Calculate distance squared (avoid square root for performance)
698
                float d_squared = dx * dx + dy * dy;
×
699

700
                // Update nearest edge if this is closer
701
                if (d_squared < min_distance) {
×
702
                    min_distance = d_squared;
×
703
                    nearest_edge = {static_cast<float>(nx), static_cast<float>(ny)};
×
704
                }
705
            }
706
        }
707
    }
708

709
    if (min_distance < radius * radius + 1) {
×
710
        std::cout << "Found edge point at (" << nearest_edge.first << ", "
×
711
                  << nearest_edge.second << "), distance: " << std::sqrt(min_distance) << std::endl;
×
712
    } else {
713
        std::cout << "No edge found within radius " << radius << std::endl;
×
714
        cv::imwrite("edges.png", _current_edges);
×
715
    }
716

717
    return nearest_edge;
×
718
}
719

720
void MediaLine_Widget::_setEraserRadius(int radius) {
×
721
    if (_selection_mode == Selection_Mode::Erase) {
×
722
        _scene->setHoverCircleRadius(static_cast<double>(radius));
×
723
    }
724
    std::cout << "Eraser radius set to: " << radius << std::endl;
×
725
}
×
726

727
void MediaLine_Widget::_toggleShowHoverCircle(bool checked) {
×
728
    _scene->setShowHoverCircle(checked);
×
729
    std::cout << "Show hover circle " << (checked ? "enabled" : "disabled") << std::endl;
×
730
}
×
731

732
void MediaLine_Widget::_setLineThickness(int thickness) {
×
733
    if (!_active_key.empty()) {
×
734
        auto line_opts = _scene->getLineConfig(_active_key);
×
735
        if (line_opts.has_value()) {
×
736
            line_opts.value()->line_thickness = thickness;
×
737
        }
738
        _scene->UpdateCanvas();
×
739
    }
740

741
    // Synchronize slider and spinbox if the signal came from one of them
742
    QObject * sender_obj = sender();
×
743
    if (sender_obj == ui->line_thickness_slider) {
×
744
        ui->line_thickness_spinbox->blockSignals(true);
×
745
        ui->line_thickness_spinbox->setValue(thickness);
×
746
        ui->line_thickness_spinbox->blockSignals(false);
×
747
    } else if (sender_obj == ui->line_thickness_spinbox) {
×
748
        ui->line_thickness_slider->blockSignals(true);
×
749
        ui->line_thickness_slider->setValue(thickness);
×
750
        ui->line_thickness_slider->blockSignals(false);
×
751
    }
752

753
    std::cout << "Line thickness set to: " << thickness << std::endl;
×
754
}
×
755

756
void MediaLine_Widget::_toggleShowPositionMarker(bool checked) {
×
757
    if (!_active_key.empty()) {
×
758
        auto line_opts = _scene->getLineConfig(_active_key);
×
759
        if (line_opts.has_value()) {
×
760
            line_opts.value()->show_position_marker = checked;
×
761
        }
762
        _scene->UpdateCanvas();
×
763
    }
764
    std::cout << "Show position marker " << (checked ? "enabled" : "disabled") << std::endl;
×
765
}
×
766

767
void MediaLine_Widget::_setPositionPercentage(int percentage) {
×
768
    if (!_active_key.empty()) {
×
769
        auto line_opts = _scene->getLineConfig(_active_key);
×
770
        if (line_opts.has_value()) {
×
771
            line_opts.value()->position_percentage = percentage;
×
772
        }
773
        _scene->UpdateCanvas();
×
774
    }
775

776
    // Synchronize slider and spinbox if the signal came from one of them
777
    QObject * sender_obj = sender();
×
778
    if (sender_obj == ui->position_percentage_slider) {
×
779
        ui->position_percentage_spinbox->blockSignals(true);
×
780
        ui->position_percentage_spinbox->setValue(percentage);
×
781
        ui->position_percentage_spinbox->blockSignals(false);
×
782
    } else if (sender_obj == ui->position_percentage_spinbox) {
×
783
        ui->position_percentage_slider->blockSignals(true);
×
784
        ui->position_percentage_slider->setValue(percentage);
×
785
        ui->position_percentage_slider->blockSignals(false);
×
786
    }
787

788
    std::cout << "Position percentage set to: " << percentage << std::endl;
×
789
}
×
790

791
void MediaLine_Widget::_toggleShowSegment(bool checked) {
×
792
    if (!_active_key.empty()) {
×
793
        auto line_opts = _scene->getLineConfig(_active_key);
×
794
        if (line_opts.has_value()) {
×
795
            line_opts.value()->show_segment = checked;
×
796
        }
797
        _scene->UpdateCanvas();
×
798
    }
799
    std::cout << "Show segment " << (checked ? "enabled" : "disabled") << std::endl;
×
800
}
×
801

802
void MediaLine_Widget::_setSegmentStartPercentage(int percentage) {
×
803
    if (_is_updating_percentages) return;
×
804
    _is_updating_percentages = true;
×
805

806
    if (!_active_key.empty()) {
×
807
        auto line_opts = _scene->getLineConfig(_active_key);
×
808
        if (line_opts.has_value()) {
×
809
            // Ensure start percentage doesn't exceed end percentage - 1%
810
            int max_start_percentage = line_opts.value()->segment_end_percentage - 1;
×
811
            if (percentage > max_start_percentage) {
×
812
                // Don't allow start to exceed end - 1%
813
                QObject * sender_obj = sender();
×
814
                if (sender_obj == ui->segment_start_slider) {
×
815
                    ui->segment_start_slider->blockSignals(true);
×
816
                    ui->segment_start_slider->setValue(max_start_percentage);
×
817
                    ui->segment_start_slider->blockSignals(false);
×
818
                } else if (sender_obj == ui->segment_start_spinbox) {
×
819
                    ui->segment_start_spinbox->blockSignals(true);
×
820
                    ui->segment_start_spinbox->setValue(max_start_percentage);
×
821
                    ui->segment_start_spinbox->blockSignals(false);
×
822
                }
823
                percentage = max_start_percentage;
×
824
            }
825

826
            line_opts.value()->segment_start_percentage = percentage;
×
827
        }
828
        _scene->UpdateCanvas();
×
829
    }
830

831
    // Synchronize the other control if this one was changed
832
    QObject * sender_obj = sender();
×
833
    if (sender_obj == ui->segment_start_slider) {
×
834
        ui->segment_start_spinbox->blockSignals(true);
×
835
        ui->segment_start_spinbox->setValue(percentage);
×
836
        ui->segment_start_spinbox->blockSignals(false);
×
837
    } else if (sender_obj == ui->segment_start_spinbox) {
×
838
        ui->segment_start_slider->blockSignals(true);
×
839
        ui->segment_start_slider->setValue(percentage);
×
840
        ui->segment_start_slider->blockSignals(false);
×
841
    }
842

843
    std::cout << "Segment start percentage set to: " << percentage << std::endl;
×
844
    _is_updating_percentages = false;
×
845
}
846

847
void MediaLine_Widget::_setSegmentEndPercentage(int percentage) {
×
848
    if (_is_updating_percentages) return;
×
849
    _is_updating_percentages = true;
×
850

851
    if (!_active_key.empty()) {
×
852
        auto line_opts = _scene->getLineConfig(_active_key);
×
853
        if (line_opts.has_value()) {
×
854
            // Ensure end percentage doesn't go below start percentage + 1%
855
            int min_end_percentage = line_opts.value()->segment_start_percentage + 1;
×
856
            if (percentage < min_end_percentage) {
×
857
                // Don't allow end to go below start + 1%
858
                QObject * sender_obj = sender();
×
859
                if (sender_obj == ui->segment_end_slider) {
×
860
                    ui->segment_end_slider->blockSignals(true);
×
861
                    ui->segment_end_slider->setValue(min_end_percentage);
×
862
                    ui->segment_end_slider->blockSignals(false);
×
863
                } else if (sender_obj == ui->segment_end_spinbox) {
×
864
                    ui->segment_end_spinbox->blockSignals(true);
×
865
                    ui->segment_end_spinbox->setValue(min_end_percentage);
×
866
                    ui->segment_end_spinbox->blockSignals(false);
×
867
                }
868
                percentage = min_end_percentage;
×
869
            }
870

871
            line_opts.value()->segment_end_percentage = percentage;
×
872
        }
873
        _scene->UpdateCanvas();
×
874
    }
875

876
    // Synchronize the other control if this one was changed
877
    QObject * sender_obj = sender();
×
878
    if (sender_obj == ui->segment_end_slider) {
×
879
        ui->segment_end_spinbox->blockSignals(true);
×
880
        ui->segment_end_spinbox->setValue(percentage);
×
881
        ui->segment_end_spinbox->blockSignals(false);
×
882
    } else if (sender_obj == ui->segment_end_spinbox) {
×
883
        ui->segment_end_slider->blockSignals(true);
×
884
        ui->segment_end_slider->setValue(percentage);
×
885
        ui->segment_end_slider->blockSignals(false);
×
886
    }
887

888
    std::cout << "Segment end percentage set to: " << percentage << std::endl;
×
889
    _is_updating_percentages = false;
×
890
}
891

892
void MediaLine_Widget::_rightClickedInVideo(qreal x_canvas, qreal y_canvas) {
×
893
    // Only handle right-clicks in Select mode and when a line is selected
NEW
894
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
NEW
895
    if (_selection_mode != Selection_Mode::Select || selected_line_index < 0 || _active_key.empty()) {
×
UNCOV
896
        return;
×
897
    }
898

899
    auto x_media = static_cast<float>(x_canvas);
×
900
    auto y_media = static_cast<float>(y_canvas);
×
901

902
    // Check if the right-click is near the selected line
903
    int nearest_line = _findNearestLine(x_media, y_media);
×
NEW
904
    if (nearest_line == selected_line_index) {
×
905
        // Show context menu at the click position
906
        QPoint global_pos = QCursor::pos();
×
907
        _showLineContextMenu(global_pos);
×
908
    }
909
}
910

911
/**
912
 * @brief Calculate the minimum distance from a point to a line segment
913
 * @param point The point to measure distance from
914
 * @param line_start Start point of the line segment
915
 * @param line_end End point of the line segment
916
 * @return The minimum distance from the point to the line segment
917
 */
918
float MediaLine_Widget::_calculateDistanceToLineSegment(Point2D<float> const & point,
×
919
                                                        Point2D<float> const & line_start,
920
                                                        Point2D<float> const & line_end) {
921
    float dx = line_end.x - line_start.x;
×
922
    float dy = line_end.y - line_start.y;
×
923

924
    // Handle degenerate case where line segment is actually a point
925
    if (dx == 0.0f && dy == 0.0f) {
×
926
        return calc_distance(point, line_start);
×
927
    }
928

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

932
    // Clamp t to [0, 1] to stay within the line segment
933
    t = std::max(0.0f, std::min(1.0f, t));
×
934

935
    // Calculate the closest point on the line segment
936
    Point2D<float> closest_point = {
×
937
            line_start.x + t * dx,
×
938
            line_start.y + t * dy};
×
939

940
    // Return the distance from the point to the closest point on the line segment
941
    return calc_distance(point, closest_point);
×
942
}
943

944
int MediaLine_Widget::_findNearestLine(float x, float y) {
×
945
    if (_active_key.empty()) {
×
946
        return -1;
×
947
    }
948

949
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
950
    if (!line_data) {
×
951
        return -1;
×
952
    }
953

954
    auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
955
    auto lines = line_data->getAtTime(current_time);
×
956

957
    if (lines.empty()) {
×
958
        return -1;
×
959
    }
960

961
    Point2D<float> click_point{x, y};
×
962
    int nearest_line_index = -1;
×
963
    float min_distance = _line_selection_threshold + 1;// Initialize beyond threshold
×
964

965
    for (int line_idx = 0; line_idx < static_cast<int>(lines.size()); ++line_idx) {
×
966
        auto const & line = lines[line_idx];
×
967

968
        if (line.empty()) {
×
969
            continue;
×
970
        }
971

972
        // Calculate distance to each line segment
973
        float min_segment_distance = std::numeric_limits<float>::max();
×
974
        for (size_t i = 0; i < line.size() - 1; ++i) {
×
975
            float segment_distance = _calculateDistanceToLineSegment(click_point, line[i], line[i + 1]);
×
976
            if (segment_distance < min_segment_distance) {
×
977
                min_segment_distance = segment_distance;
×
978
            }
979
        }
980

981
        // Also check distance to vertices for completeness
982
        float min_vertex_distance = std::numeric_limits<float>::max();
×
983
        for (auto const & vertex: line) {
×
984
            float vertex_distance = calc_distance(click_point, vertex);
×
985
            if (vertex_distance < min_vertex_distance) {
×
986
                min_vertex_distance = vertex_distance;
×
987
            }
988
        }
989

990
        float line_distance = std::min(min_vertex_distance, min_segment_distance);
×
991

992
        if (line_distance < min_distance) {
×
993
            min_distance = line_distance;
×
994
            nearest_line_index = line_idx;
×
995
            std::cout << "  -> New closest line!" << std::endl;
×
996
        }
997
    }
998

999
    return (min_distance <= _line_selection_threshold) ? nearest_line_index : -1;
×
1000
}
×
1001

1002
void MediaLine_Widget::_selectLine(int line_index) {
×
1003
    _selected_line_index = line_index;
×
1004

1005
    // Update the line display options to show the selected line differently
1006
    if (!_active_key.empty()) {
×
1007
        auto line_opts = _scene->getLineConfig(_active_key);
×
1008
        if (line_opts.has_value()) {
×
1009
            line_opts.value()->selected_line_index = line_index;
×
1010
        }
1011
        _scene->UpdateCanvas();
×
1012
    }
1013
}
×
1014

1015
void MediaLine_Widget::_clearLineSelection() {
×
1016
    _selected_line_index = -1;
×
1017

1018
    // Update the line display options to clear selection
1019
    if (!_active_key.empty()) {
×
1020
        auto line_opts = _scene->getLineConfig(_active_key);
×
1021
        if (line_opts.has_value()) {
×
1022
            line_opts.value()->selected_line_index = -1;
×
1023
        }
1024
        _scene->UpdateCanvas();
×
1025
    }
1026
}
×
1027

1028
void MediaLine_Widget::_showLineContextMenu(QPoint const & position) {
×
1029
    QMenu context_menu(this);
×
1030

1031
    // Create Move To submenu
1032
    QMenu * move_menu = context_menu.addMenu("Move Line To");
×
1033
    QMenu * copy_menu = context_menu.addMenu("Copy Line To");
×
1034

1035
    // Get available LineData keys
1036
    auto available_keys = _getAvailableLineDataKeys();
×
1037

1038
    for (auto const & key: available_keys) {
×
1039
        if (key != _active_key) {// Don't include the current key
×
1040
            // Add to Move menu
1041
            QAction * move_action = move_menu->addAction(QString::fromStdString(key));
×
1042
            connect(move_action, &QAction::triggered, [this, key]() {
×
1043
                _moveLineToTarget(key);
×
1044
            });
×
1045

1046
            // Add to Copy menu
1047
            QAction * copy_action = copy_menu->addAction(QString::fromStdString(key));
×
1048
            connect(copy_action, &QAction::triggered, [this, key]() {
×
1049
                _copyLineToTarget(key);
×
1050
            });
×
1051
        }
1052
    }
1053

1054
    // Disable menus if no other LineData available
1055
    if (available_keys.size() <= 1) {
×
1056
        move_menu->setEnabled(false);
×
1057
        copy_menu->setEnabled(false);
×
1058
    }
1059

1060
    context_menu.exec(position);
×
1061
}
×
1062

1063
std::vector<std::string> MediaLine_Widget::_getAvailableLineDataKeys() {
×
1064
    return _data_manager->getKeys<LineData>();
×
1065
}
1066

1067
void MediaLine_Widget::_moveLineToTarget(std::string const & target_key) {
×
NEW
1068
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
NEW
1069
    if (selected_line_index < 0 || _active_key.empty()) {
×
UNCOV
1070
        return;
×
1071
    }
1072

1073
    auto source_line_data = _data_manager->getData<LineData>(_active_key);
×
1074
    auto target_line_data = _data_manager->getData<LineData>(target_key);
×
1075

1076
    if (!source_line_data || !target_line_data) {
×
1077
        std::cerr << "Could not retrieve source or target LineData" << std::endl;
×
1078
        return;
×
1079
    }
1080

1081
    auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
1082
    auto lines = source_line_data->getAtTime(current_time);
×
1083

NEW
1084
    if (selected_line_index >= static_cast<int>(lines.size())) {
×
1085
        std::cerr << "Selected line index out of bounds" << std::endl;
×
1086
        return;
×
1087
    }
1088

1089
    // Get the selected line
NEW
1090
    Line2D selected_line = lines[selected_line_index];
×
1091

1092
    // Add to target
1093
    target_line_data->addAtTime(current_time, selected_line);
×
1094

1095
    // Remove from source by rebuilding the vector without the selected line
1096
    std::vector<Line2D> remaining_lines;
×
1097
    for (int i = 0; i < static_cast<int>(lines.size()); ++i) {
×
NEW
1098
        if (i != selected_line_index) {
×
1099
            remaining_lines.push_back(lines[i]);
×
1100
        }
1101
    }
1102

1103
    // Clear and rebuild source lines
1104
    source_line_data->clearAtTime(TimeFrameIndex(current_time));
×
1105
    for (auto const & line: remaining_lines) {
×
1106
        source_line_data->addAtTime(current_time, line);
×
1107
    }
1108

1109
    // Clear selection since the line was moved
NEW
1110
    _scene->clearAllSelections();
×
1111

1112
    std::cout << "Moved line from " << _active_key << " to " << target_key << std::endl;
×
1113
}
×
1114

1115
void MediaLine_Widget::_copyLineToTarget(std::string const & target_key) {
×
NEW
1116
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
NEW
1117
    if (selected_line_index < 0 || _active_key.empty()) {
×
UNCOV
1118
        return;
×
1119
    }
1120

1121
    auto source_line_data = _data_manager->getData<LineData>(_active_key);
×
1122
    auto target_line_data = _data_manager->getData<LineData>(target_key);
×
1123

1124
    if (!source_line_data || !target_line_data) {
×
1125
        std::cerr << "Could not retrieve source or target LineData" << std::endl;
×
1126
        return;
×
1127
    }
1128

1129
    auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
1130
    auto lines = source_line_data->getAtTime(current_time);
×
1131

NEW
1132
    if (selected_line_index >= static_cast<int>(lines.size())) {
×
1133
        std::cerr << "Selected line index out of bounds" << std::endl;
×
1134
        return;
×
1135
    }
1136

1137
    // Get the selected line and copy it to target
NEW
1138
    Line2D selected_line = lines[selected_line_index];
×
1139
    target_line_data->addAtTime(current_time, selected_line);
×
1140

1141
    std::cout << "Copied line from " << _active_key << " to " << target_key << std::endl;
×
1142
}
×
1143

1144
void MediaLine_Widget::_addPointToDrawAllFrames(float x_media, float y_media) {
×
1145
    if (_drawAllFramesSelectionWidget && _drawAllFramesSelectionWidget->isDrawingActive()) {
×
1146
        _drawAllFramesSelectionWidget->addPoint(Point2D<float>{x_media, y_media});
×
1147
        std::cout << "Added point (" << x_media << ", " << y_media << ") to all frames line" << std::endl;
×
1148
    }
1149
}
×
1150

1151
void MediaLine_Widget::_applyLineToAllFrames() {
×
1152
    if (!_drawAllFramesSelectionWidget || _active_key.empty()) {
×
1153
        std::cout << "Cannot apply line to all frames: widget not available or no active key" << std::endl;
×
1154
        return;
×
1155
    }
1156

1157
    auto line_points = _drawAllFramesSelectionWidget->getCurrentLinePoints();
×
1158
    if (line_points.empty()) {
×
1159
        std::cout << "No line points to apply to all frames" << std::endl;
×
1160
        return;
×
1161
    }
1162

1163
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
1164
    if (!line_data) {
×
1165
        std::cout << "No line data available for active key" << std::endl;
×
1166
        return;
×
1167
    }
1168

1169
    // Get all frame times
1170
    auto frame_times = _getAllFrameTimes();
×
1171
    if (frame_times.empty()) {
×
1172
        std::cout << "No frame times available" << std::endl;
×
1173
        return;
×
1174
    }
1175

1176
    // Create the line from points
1177
    Line2D line_to_apply(line_points);
×
1178

1179
    // Apply the line to all frames
1180
    int frames_processed = 0;
×
1181
    for (auto const & frame_time: frame_times) {
×
1182
        line_data->addAtTime(frame_time, line_to_apply, false);// Don't notify for each frame
×
1183
        frames_processed++;
×
1184
    }
1185

1186
    // Notify observers once at the end
1187
    line_data->notifyObservers();
×
1188

1189
    // Clear the line points after applying
1190
    _drawAllFramesSelectionWidget->clearLinePoints();
×
1191

1192
    std::cout << "Applied line to " << frames_processed << " frames" << std::endl;
×
1193
    _scene->UpdateCanvas();
×
1194
}
×
1195

1196
std::vector<TimeFrameIndex> MediaLine_Widget::_getAllFrameTimes() {
×
1197
    std::vector<TimeFrameIndex> frame_times;
×
1198

1199
    // Get media data to determine total frame count
1200
    auto media_data = _data_manager->getData<MediaData>("media");
×
1201
    if (!media_data) {
×
1202
        std::cout << "No media data available" << std::endl;
×
1203
        return frame_times;
×
1204
    }
1205

1206
    int total_frames = media_data->getTotalFrameCount();
×
1207
    if (total_frames <= 0) {
×
1208
        std::cout << "Invalid frame count: " << total_frames << std::endl;
×
1209
        return frame_times;
×
1210
    }
1211

1212
    // Create TimeFrameIndex for each frame
1213
    frame_times.reserve(total_frames);
×
1214
    for (int i = 0; i < total_frames; ++i) {
×
1215
        frame_times.emplace_back(i);
×
1216
    }
1217

1218
    return frame_times;
1219
}
×
1220

NEW
1221
int MediaLine_Widget::_getSelectedLineIndexFromGroupSystem() const {
×
1222
    // Get the selected entities from the group system
NEW
1223
    auto selected_entities = _scene->getSelectedEntities();
×
1224
    
1225
    // If no entities are selected, return -1
NEW
1226
    if (selected_entities.empty()) {
×
NEW
1227
        return -1;
×
1228
    }
1229
    
1230
    // For now, just return the first selected entity as the line index
1231
    // EntityId for lines corresponds to their index in the line data
NEW
1232
    EntityId first_selected = *selected_entities.begin();
×
NEW
1233
    return static_cast<int>(first_selected);
×
NEW
1234
}
×
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