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

paulmthompson / WhiskerToolbox / 18096850108

29 Sep 2025 12:24PM UTC coverage: 70.143% (-0.09%) from 70.231%
18096850108

push

github

paulmthompson
better line drawing selection

15 of 92 new or added lines in 3 files covered. (16.3%)

21 existing lines in 3 files now uncovered.

44250 of 63085 relevant lines covered (70.14%)

1121.81 hits per line

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

14.42
/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
    connect(_drawAllFramesSelectionWidget, &line_widget::LineDrawAllFramesSelectionWidget::linePointsUpdated,
9✔
154
            this, &MediaLine_Widget::_updateTemporaryLineFromWidget);
6✔
155

156
    ui->mode_stacked_widget->setCurrentIndex(0);
3✔
157
}
3✔
158

159
MediaLine_Widget::~MediaLine_Widget() {
6✔
160
    delete ui;
3✔
161
}
6✔
162

163
void MediaLine_Widget::showEvent(QShowEvent * event) {
×
164

165
    static_cast<void>(event);
166

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

172
void MediaLine_Widget::hideEvent(QHideEvent * event) {
3✔
173

174
    static_cast<void>(event);
175

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

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

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

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

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

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

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

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

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

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

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

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

240
            // Reset line selection and update slider
241
            _current_line_index = 0;
×
242

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

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

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

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

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

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

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

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

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

300
    auto lines = line_data->getAtTime(current_time);
×
301

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

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

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

355
    if (use_edge_snapping && _edge_snapping_enabled) {
×
356

357
        if (_current_edges.empty()) {
×
358
            _detectEdges();
×
359
        }
360

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

510
    // Enable/disable temporary line visualization for DrawAllFrames mode
511
    if (_selection_mode == Selection_Mode::DrawAllFrames) {
3✔
NEW
512
        _scene->setShowTemporaryLine(true);
×
513
    } else {
514
        _scene->setShowTemporaryLine(false);
3✔
515
    }
516
}
3✔
517

518
void MediaLine_Widget::_toggleShowPoints(bool checked) {
×
519
    if (!_active_key.empty()) {
×
520
        auto line_opts = _scene->getLineConfig(_active_key);
×
521
        if (line_opts.has_value()) {
×
522
            line_opts.value()->show_points = checked;
×
523
        }
524
        _scene->UpdateCanvas();
×
525
    }
526
}
×
527

528
void MediaLine_Widget::_toggleEdgeSnapping(bool checked) {
×
529
    _edge_snapping_enabled = checked;
×
530

531
    if (!_active_key.empty()) {
×
532
        auto line_opts = _scene->getLineConfig(_active_key);
×
533
        if (line_opts.has_value()) {
×
534
            line_opts.value()->edge_snapping = checked;
×
535
        }
536

537
        // If enabling edge snapping, perform edge detection immediately
538
        if (checked) {
×
539
            _detectEdges();
×
540
        } else {
541
            // Clear cached edges when disabling
542
            _current_edges.release();
×
543
        }
544

545
        _scene->UpdateCanvas();
×
546
    }
547

548
    std::cout << "Edge snapping " << (checked ? "enabled" : "disabled") << std::endl;
×
549
}
×
550

551
void MediaLine_Widget::LoadFrame(int frame_id) {
×
552
    // Update the widget with the new frame
553
    // This could involve refreshing displays or updating UI elements
554
    // specific to the current frame
555

556
    // If we have an active line, we might want to update some UI
557
    // based on line data at this frame
558
    if (!_active_key.empty()) {
×
559
        auto line_data = _data_manager->getData<LineData>(_active_key);
×
560
        if (line_data) {
×
561
            auto lines = line_data->getAtTime(TimeFrameIndex(frame_id));
×
562

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

566
            // Disconnect and reconnect to avoid triggering slider value changed signals
567
            // during this programmatic update
568
            ui->line_select_slider->blockSignals(true);
×
569

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

574
            // If the current slider value exceeds the new maximum, adjust it
575
            if (ui->line_select_slider->value() > ui->line_select_slider->maximum()) {
×
576
                ui->line_select_slider->setValue(ui->line_select_slider->maximum());
×
577
            }
578

579
            ui->line_select_slider->blockSignals(false);
×
580

581
            // Update slider enabled state
582
            ui->line_select_slider->setEnabled(num_lines > 1);
×
583

584
            std::cout << "Frame " << frame_id << ": Updated line selector for "
×
585
                      << num_lines << " lines in " << _active_key << std::endl;
×
586
        }
×
587
    }
×
588
}
×
589

590
void MediaLine_Widget::_lineSelectionChanged(int index) {
×
591
    if (_current_line_index == index) {
×
592
        return;// No change
×
593
    }
594

595
    _current_line_index = index;
×
596
    std::cout << "Selected line index: " << _current_line_index << std::endl;
×
597

598
    // Update any UI or visualization based on the selected line
599
    if (!_active_key.empty()) {
×
600
        auto line_data = _data_manager->getData<LineData>(_active_key);
×
601
        if (line_data) {
×
602
            auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
603
            auto lines = line_data->getAtTime(current_time);
×
604

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

609
                // Request canvas update to reflect any visualization changes
610
                _scene->UpdateCanvas();
×
611
            }
612
        }
×
613
    }
×
614
}
615

616
void MediaLine_Widget::_setEdgeThreshold(int threshold) {
×
617
    _edge_threshold = threshold;
×
618
    std::cout << "Edge threshold set to: " << threshold << std::endl;
×
619
}
×
620

621
void MediaLine_Widget::_setEdgeSearchRadius(int radius) {
×
622
    _edge_search_radius = radius;
×
623
    std::cout << "Edge search radius set to: " << radius << std::endl;
×
624
}
×
625

626
void MediaLine_Widget::_detectEdges() {
×
627

628
    auto media = _data_manager->getData<MediaData>("media");
×
629
    if (!media) {
×
630
        std::cout << "No media data available for edge detection" << std::endl;
×
631
        return;
×
632
    }
633

634
    auto const current_time = _data_manager->getCurrentTime();
×
635
    auto frame_data = media->getProcessedData(current_time);
×
636

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

659
    //cv::Mat blurred;
660
    //cv::GaussianBlur(gray_image, blurred, cv::Size(5, 5), 1.5);
661
    //cv::Canny(blurred, _current_edges, _edge_threshold / 2, _edge_threshold);
662

663
    cv::Canny(gray_image, _current_edges, _edge_threshold / 2, _edge_threshold);
×
664

665
    std::cout << "Edge detection completed for frame " << current_time << std::endl;
×
666
    std::cout << "Edges detected: " << _current_edges.size() << std::endl;
×
667
}
×
668

669
std::pair<float, float> MediaLine_Widget::_findNearestEdge(float x, float y) {
×
670
    if (_current_edges.empty()) {
×
671
        return {x, y};
×
672
    }
673

674
    // Round x and y to integers (image coordinates)
675
    int x_int = static_cast<int>(std::round(x));
×
676
    int y_int = static_cast<int>(std::round(y));
×
677

678
    // Define search radius and initialize variables
679
    int radius = _edge_search_radius;
×
680
    float min_distance = radius * radius + 1;     // Initialize to something larger than possible
×
681
    std::pair<float, float> nearest_edge = {x, y};// Default to original point
×
682

683
    // Get image dimensions
684
    int width = _current_edges.cols;
×
685
    int height = _current_edges.rows;
×
686

687
    // Check if the point is within image bounds
688
    if (x_int < 0 || x_int >= width || y_int < 0 || y_int >= height) {
×
689
        std::cout << "Click point outside image bounds" << std::endl;
×
690
        return {x, y};
×
691
    }
692

693
    // Search in a square region around the clicked point
694
    for (int dy = -radius; dy <= radius; dy++) {
×
695
        for (int dx = -radius; dx <= radius; dx++) {
×
696
            // Calculate current point to check
697
            int nx = x_int + dx;
×
698
            int ny = y_int + dy;
×
699

700
            if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
×
701
                continue;
×
702
            }
703

704
            // Check if this is an edge pixel
705
            if (_current_edges.at<uchar>(ny, nx) > 0) {
×
706
                // Calculate distance squared (avoid square root for performance)
707
                float d_squared = dx * dx + dy * dy;
×
708

709
                // Update nearest edge if this is closer
710
                if (d_squared < min_distance) {
×
711
                    min_distance = d_squared;
×
712
                    nearest_edge = {static_cast<float>(nx), static_cast<float>(ny)};
×
713
                }
714
            }
715
        }
716
    }
717

718
    if (min_distance < radius * radius + 1) {
×
719
        std::cout << "Found edge point at (" << nearest_edge.first << ", "
×
720
                  << nearest_edge.second << "), distance: " << std::sqrt(min_distance) << std::endl;
×
721
    } else {
722
        std::cout << "No edge found within radius " << radius << std::endl;
×
723
        cv::imwrite("edges.png", _current_edges);
×
724
    }
725

726
    return nearest_edge;
×
727
}
728

729
void MediaLine_Widget::_setEraserRadius(int radius) {
×
730
    if (_selection_mode == Selection_Mode::Erase) {
×
731
        _scene->setHoverCircleRadius(static_cast<double>(radius));
×
732
    }
733
    std::cout << "Eraser radius set to: " << radius << std::endl;
×
734
}
×
735

736
void MediaLine_Widget::_toggleShowHoverCircle(bool checked) {
×
737
    _scene->setShowHoverCircle(checked);
×
738
    std::cout << "Show hover circle " << (checked ? "enabled" : "disabled") << std::endl;
×
739
}
×
740

741
void MediaLine_Widget::_setLineThickness(int thickness) {
×
742
    if (!_active_key.empty()) {
×
743
        auto line_opts = _scene->getLineConfig(_active_key);
×
744
        if (line_opts.has_value()) {
×
745
            line_opts.value()->line_thickness = thickness;
×
746
        }
747
        _scene->UpdateCanvas();
×
748
    }
749

750
    // Synchronize slider and spinbox if the signal came from one of them
751
    QObject * sender_obj = sender();
×
752
    if (sender_obj == ui->line_thickness_slider) {
×
753
        ui->line_thickness_spinbox->blockSignals(true);
×
754
        ui->line_thickness_spinbox->setValue(thickness);
×
755
        ui->line_thickness_spinbox->blockSignals(false);
×
756
    } else if (sender_obj == ui->line_thickness_spinbox) {
×
757
        ui->line_thickness_slider->blockSignals(true);
×
758
        ui->line_thickness_slider->setValue(thickness);
×
759
        ui->line_thickness_slider->blockSignals(false);
×
760
    }
761

762
    std::cout << "Line thickness set to: " << thickness << std::endl;
×
763
}
×
764

765
void MediaLine_Widget::_toggleShowPositionMarker(bool checked) {
×
766
    if (!_active_key.empty()) {
×
767
        auto line_opts = _scene->getLineConfig(_active_key);
×
768
        if (line_opts.has_value()) {
×
769
            line_opts.value()->show_position_marker = checked;
×
770
        }
771
        _scene->UpdateCanvas();
×
772
    }
773
    std::cout << "Show position marker " << (checked ? "enabled" : "disabled") << std::endl;
×
774
}
×
775

776
void MediaLine_Widget::_setPositionPercentage(int percentage) {
×
777
    if (!_active_key.empty()) {
×
778
        auto line_opts = _scene->getLineConfig(_active_key);
×
779
        if (line_opts.has_value()) {
×
780
            line_opts.value()->position_percentage = percentage;
×
781
        }
782
        _scene->UpdateCanvas();
×
783
    }
784

785
    // Synchronize slider and spinbox if the signal came from one of them
786
    QObject * sender_obj = sender();
×
787
    if (sender_obj == ui->position_percentage_slider) {
×
788
        ui->position_percentage_spinbox->blockSignals(true);
×
789
        ui->position_percentage_spinbox->setValue(percentage);
×
790
        ui->position_percentage_spinbox->blockSignals(false);
×
791
    } else if (sender_obj == ui->position_percentage_spinbox) {
×
792
        ui->position_percentage_slider->blockSignals(true);
×
793
        ui->position_percentage_slider->setValue(percentage);
×
794
        ui->position_percentage_slider->blockSignals(false);
×
795
    }
796

797
    std::cout << "Position percentage set to: " << percentage << std::endl;
×
798
}
×
799

800
void MediaLine_Widget::_toggleShowSegment(bool checked) {
×
801
    if (!_active_key.empty()) {
×
802
        auto line_opts = _scene->getLineConfig(_active_key);
×
803
        if (line_opts.has_value()) {
×
804
            line_opts.value()->show_segment = checked;
×
805
        }
806
        _scene->UpdateCanvas();
×
807
    }
808
    std::cout << "Show segment " << (checked ? "enabled" : "disabled") << std::endl;
×
809
}
×
810

811
void MediaLine_Widget::_setSegmentStartPercentage(int percentage) {
×
812
    if (_is_updating_percentages) return;
×
813
    _is_updating_percentages = true;
×
814

815
    if (!_active_key.empty()) {
×
816
        auto line_opts = _scene->getLineConfig(_active_key);
×
817
        if (line_opts.has_value()) {
×
818
            // Ensure start percentage doesn't exceed end percentage - 1%
819
            int max_start_percentage = line_opts.value()->segment_end_percentage - 1;
×
820
            if (percentage > max_start_percentage) {
×
821
                // Don't allow start to exceed end - 1%
822
                QObject * sender_obj = sender();
×
823
                if (sender_obj == ui->segment_start_slider) {
×
824
                    ui->segment_start_slider->blockSignals(true);
×
825
                    ui->segment_start_slider->setValue(max_start_percentage);
×
826
                    ui->segment_start_slider->blockSignals(false);
×
827
                } else if (sender_obj == ui->segment_start_spinbox) {
×
828
                    ui->segment_start_spinbox->blockSignals(true);
×
829
                    ui->segment_start_spinbox->setValue(max_start_percentage);
×
830
                    ui->segment_start_spinbox->blockSignals(false);
×
831
                }
832
                percentage = max_start_percentage;
×
833
            }
834

835
            line_opts.value()->segment_start_percentage = percentage;
×
836
        }
837
        _scene->UpdateCanvas();
×
838
    }
839

840
    // Synchronize the other control if this one was changed
841
    QObject * sender_obj = sender();
×
842
    if (sender_obj == ui->segment_start_slider) {
×
843
        ui->segment_start_spinbox->blockSignals(true);
×
844
        ui->segment_start_spinbox->setValue(percentage);
×
845
        ui->segment_start_spinbox->blockSignals(false);
×
846
    } else if (sender_obj == ui->segment_start_spinbox) {
×
847
        ui->segment_start_slider->blockSignals(true);
×
848
        ui->segment_start_slider->setValue(percentage);
×
849
        ui->segment_start_slider->blockSignals(false);
×
850
    }
851

852
    std::cout << "Segment start percentage set to: " << percentage << std::endl;
×
853
    _is_updating_percentages = false;
×
854
}
855

856
void MediaLine_Widget::_setSegmentEndPercentage(int percentage) {
×
857
    if (_is_updating_percentages) return;
×
858
    _is_updating_percentages = true;
×
859

860
    if (!_active_key.empty()) {
×
861
        auto line_opts = _scene->getLineConfig(_active_key);
×
862
        if (line_opts.has_value()) {
×
863
            // Ensure end percentage doesn't go below start percentage + 1%
864
            int min_end_percentage = line_opts.value()->segment_start_percentage + 1;
×
865
            if (percentage < min_end_percentage) {
×
866
                // Don't allow end to go below start + 1%
867
                QObject * sender_obj = sender();
×
868
                if (sender_obj == ui->segment_end_slider) {
×
869
                    ui->segment_end_slider->blockSignals(true);
×
870
                    ui->segment_end_slider->setValue(min_end_percentage);
×
871
                    ui->segment_end_slider->blockSignals(false);
×
872
                } else if (sender_obj == ui->segment_end_spinbox) {
×
873
                    ui->segment_end_spinbox->blockSignals(true);
×
874
                    ui->segment_end_spinbox->setValue(min_end_percentage);
×
875
                    ui->segment_end_spinbox->blockSignals(false);
×
876
                }
877
                percentage = min_end_percentage;
×
878
            }
879

880
            line_opts.value()->segment_end_percentage = percentage;
×
881
        }
882
        _scene->UpdateCanvas();
×
883
    }
884

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

897
    std::cout << "Segment end percentage set to: " << percentage << std::endl;
×
898
    _is_updating_percentages = false;
×
899
}
900

901
void MediaLine_Widget::_rightClickedInVideo(qreal x_canvas, qreal y_canvas) {
×
902
    // Only handle right-clicks in Select mode and when a line is selected
903
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
904
    if (_selection_mode != Selection_Mode::Select || selected_line_index < 0 || _active_key.empty()) {
×
905
        return;
×
906
    }
907

908
    auto x_media = static_cast<float>(x_canvas);
×
909
    auto y_media = static_cast<float>(y_canvas);
×
910

911
    // Check if the right-click is near the selected line
912
    int nearest_line = _findNearestLine(x_media, y_media);
×
913
    if (nearest_line == selected_line_index) {
×
914
        // Show context menu at the click position
915
        QPoint global_pos = QCursor::pos();
×
916
        _showLineContextMenu(global_pos);
×
917
    }
918
}
919

920
/**
921
 * @brief Calculate the minimum distance from a point to a line segment
922
 * @param point The point to measure distance from
923
 * @param line_start Start point of the line segment
924
 * @param line_end End point of the line segment
925
 * @return The minimum distance from the point to the line segment
926
 */
927
float MediaLine_Widget::_calculateDistanceToLineSegment(Point2D<float> const & point,
×
928
                                                        Point2D<float> const & line_start,
929
                                                        Point2D<float> const & line_end) {
930
    float dx = line_end.x - line_start.x;
×
931
    float dy = line_end.y - line_start.y;
×
932

933
    // Handle degenerate case where line segment is actually a point
934
    if (dx == 0.0f && dy == 0.0f) {
×
935
        return calc_distance(point, line_start);
×
936
    }
937

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

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

944
    // Calculate the closest point on the line segment
945
    Point2D<float> closest_point = {
×
946
            line_start.x + t * dx,
×
947
            line_start.y + t * dy};
×
948

949
    // Return the distance from the point to the closest point on the line segment
950
    return calc_distance(point, closest_point);
×
951
}
952

953
int MediaLine_Widget::_findNearestLine(float x, float y) {
×
954
    if (_active_key.empty()) {
×
955
        return -1;
×
956
    }
957

958
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
959
    if (!line_data) {
×
960
        return -1;
×
961
    }
962

963
    auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
964
    auto lines = line_data->getAtTime(current_time);
×
965

966
    if (lines.empty()) {
×
967
        return -1;
×
968
    }
969

970
    Point2D<float> click_point{x, y};
×
971
    int nearest_line_index = -1;
×
972
    float min_distance = _line_selection_threshold + 1;// Initialize beyond threshold
×
973

974
    for (int line_idx = 0; line_idx < static_cast<int>(lines.size()); ++line_idx) {
×
975
        auto const & line = lines[line_idx];
×
976

977
        if (line.empty()) {
×
978
            continue;
×
979
        }
980

981
        // Calculate distance to each line segment
982
        float min_segment_distance = std::numeric_limits<float>::max();
×
983
        for (size_t i = 0; i < line.size() - 1; ++i) {
×
984
            float segment_distance = _calculateDistanceToLineSegment(click_point, line[i], line[i + 1]);
×
985
            if (segment_distance < min_segment_distance) {
×
986
                min_segment_distance = segment_distance;
×
987
            }
988
        }
989

990
        // Also check distance to vertices for completeness
991
        float min_vertex_distance = std::numeric_limits<float>::max();
×
992
        for (auto const & vertex: line) {
×
993
            float vertex_distance = calc_distance(click_point, vertex);
×
994
            if (vertex_distance < min_vertex_distance) {
×
995
                min_vertex_distance = vertex_distance;
×
996
            }
997
        }
998

999
        float line_distance = std::min(min_vertex_distance, min_segment_distance);
×
1000

1001
        if (line_distance < min_distance) {
×
1002
            min_distance = line_distance;
×
1003
            nearest_line_index = line_idx;
×
1004
            std::cout << "  -> New closest line!" << std::endl;
×
1005
        }
1006
    }
1007

1008
    return (min_distance <= _line_selection_threshold) ? nearest_line_index : -1;
×
1009
}
×
1010

1011
void MediaLine_Widget::_selectLine(int line_index) {
×
1012
    _selected_line_index = line_index;
×
1013

1014
    // Update the line display options to show the selected line differently
1015
    if (!_active_key.empty()) {
×
1016
        auto line_opts = _scene->getLineConfig(_active_key);
×
1017
        if (line_opts.has_value()) {
×
1018
            line_opts.value()->selected_line_index = line_index;
×
1019
        }
1020
        _scene->UpdateCanvas();
×
1021
    }
1022
}
×
1023

1024
void MediaLine_Widget::_clearLineSelection() {
×
1025
    _selected_line_index = -1;
×
1026

1027
    // Update the line display options to clear selection
1028
    if (!_active_key.empty()) {
×
1029
        auto line_opts = _scene->getLineConfig(_active_key);
×
1030
        if (line_opts.has_value()) {
×
1031
            line_opts.value()->selected_line_index = -1;
×
1032
        }
1033
        _scene->UpdateCanvas();
×
1034
    }
1035
}
×
1036

1037
void MediaLine_Widget::_showLineContextMenu(QPoint const & position) {
×
1038
    QMenu context_menu(this);
×
1039

1040
    // Create Move To submenu
1041
    QMenu * move_menu = context_menu.addMenu("Move Line To");
×
1042
    QMenu * copy_menu = context_menu.addMenu("Copy Line To");
×
1043

1044
    // Get available LineData keys
1045
    auto available_keys = _getAvailableLineDataKeys();
×
1046

1047
    for (auto const & key: available_keys) {
×
1048
        if (key != _active_key) {// Don't include the current key
×
1049
            // Add to Move menu
1050
            QAction * move_action = move_menu->addAction(QString::fromStdString(key));
×
1051
            connect(move_action, &QAction::triggered, [this, key]() {
×
1052
                _moveLineToTarget(key);
×
1053
            });
×
1054

1055
            // Add to Copy menu
1056
            QAction * copy_action = copy_menu->addAction(QString::fromStdString(key));
×
1057
            connect(copy_action, &QAction::triggered, [this, key]() {
×
1058
                _copyLineToTarget(key);
×
1059
            });
×
1060
        }
1061
    }
1062

1063
    // Disable menus if no other LineData available
1064
    if (available_keys.size() <= 1) {
×
1065
        move_menu->setEnabled(false);
×
1066
        copy_menu->setEnabled(false);
×
1067
    }
1068

1069
    context_menu.exec(position);
×
1070
}
×
1071

1072
std::vector<std::string> MediaLine_Widget::_getAvailableLineDataKeys() {
×
1073
    return _data_manager->getKeys<LineData>();
×
1074
}
1075

1076
void MediaLine_Widget::_moveLineToTarget(std::string const & target_key) {
×
1077
    int selected_line_index = _getSelectedLineIndexFromGroupSystem();
×
1078
    if (selected_line_index < 0 || _active_key.empty()) {
×
1079
        return;
×
1080
    }
1081

1082
    auto source_line_data = _data_manager->getData<LineData>(_active_key);
×
1083
    auto target_line_data = _data_manager->getData<LineData>(target_key);
×
1084

1085
    if (!source_line_data || !target_line_data) {
×
1086
        std::cerr << "Could not retrieve source or target LineData" << std::endl;
×
1087
        return;
×
1088
    }
1089

1090
    auto current_time = TimeFrameIndex(_data_manager->getCurrentTime());
×
1091
    auto lines = source_line_data->getAtTime(current_time);
×
1092

1093
    if (selected_line_index >= static_cast<int>(lines.size())) {
×
1094
        std::cerr << "Selected line index out of bounds" << std::endl;
×
1095
        return;
×
1096
    }
1097

1098
    // Get the selected line
1099
    Line2D selected_line = lines[selected_line_index];
×
1100

1101
    // Add to target
1102
    target_line_data->addAtTime(current_time, selected_line);
×
1103

1104
    // Remove from source by rebuilding the vector without the selected line
1105
    std::vector<Line2D> remaining_lines;
×
1106
    for (int i = 0; i < static_cast<int>(lines.size()); ++i) {
×
1107
        if (i != selected_line_index) {
×
1108
            remaining_lines.push_back(lines[i]);
×
1109
        }
1110
    }
1111

1112
    // Clear and rebuild source lines
1113
    source_line_data->clearAtTime(TimeFrameIndex(current_time));
×
1114
    for (auto const & line: remaining_lines) {
×
1115
        source_line_data->addAtTime(current_time, line);
×
1116
    }
1117

1118
    // Clear selection since the line was moved
1119
    _scene->clearAllSelections();
×
1120

1121
    std::cout << "Moved line from " << _active_key << " to " << target_key << std::endl;
×
1122
}
×
1123

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

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

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

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

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

1146
    // Get the selected line and copy it to target
1147
    Line2D selected_line = lines[selected_line_index];
×
1148
    target_line_data->addAtTime(current_time, selected_line);
×
1149

1150
    std::cout << "Copied line from " << _active_key << " to " << target_key << std::endl;
×
1151
}
×
1152

1153
void MediaLine_Widget::_addPointToDrawAllFrames(float x_media, float y_media) {
×
1154
    if (_drawAllFramesSelectionWidget && _drawAllFramesSelectionWidget->isDrawingActive()) {
×
1155
        // Store the points in default media coordinates for the temporary line
1156
        _drawAllFramesSelectionWidget->addPoint(Point2D<float>{x_media, y_media});
×
1157
        
1158
        // Update the temporary line visualization using default aspect ratios
NEW
1159
        auto current_points = _drawAllFramesSelectionWidget->getCurrentLinePoints();
×
NEW
1160
        _scene->updateTemporaryLine(current_points, ""); // Use default aspect ratios
×
1161
        
1162
    }
×
1163
}
×
1164

1165
void MediaLine_Widget::_applyLineToAllFrames() {
×
1166
    if (!_drawAllFramesSelectionWidget || _active_key.empty()) {
×
1167
        std::cout << "Cannot apply line to all frames: widget not available or no active key" << std::endl;
×
1168
        return;
×
1169
    }
1170

1171
    auto line_points = _drawAllFramesSelectionWidget->getCurrentLinePoints();
×
1172
    if (line_points.empty()) {
×
1173
        std::cout << "No line points to apply to all frames" << std::endl;
×
1174
        return;
×
1175
    }
1176

1177
    auto line_data = _data_manager->getData<LineData>(_active_key);
×
1178
    if (!line_data) {
×
1179
        std::cout << "No line data available for active key" << std::endl;
×
1180
        return;
×
1181
    }
1182

1183
    // Get all frame times
1184
    auto frame_times = _getAllFrameTimes();
×
1185
    if (frame_times.empty()) {
×
1186
        std::cout << "No frame times available" << std::endl;
×
1187
        return;
×
1188
    }
1189

1190
    // Convert coordinates from default media to line-specific coordinates if needed
NEW
1191
    Line2D line_to_apply;
×
NEW
1192
    if (!_active_key.empty()) {
×
NEW
1193
        auto line_data_for_conversion = _data_manager->getData<LineData>(_active_key);
×
NEW
1194
        if (line_data_for_conversion) {
×
NEW
1195
            auto image_size = line_data_for_conversion->getImageSize();
×
1196
            
1197
            // If the line data has specific image dimensions, convert coordinates
NEW
1198
            if (image_size.width != -1 && image_size.height != -1) {
×
NEW
1199
                float default_xAspect = _scene->getXAspect();
×
NEW
1200
                float default_yAspect = _scene->getYAspect();
×
NEW
1201
                float line_xAspect = static_cast<float>(_scene->getCanvasSize().first) / image_size.width;
×
NEW
1202
                float line_yAspect = static_cast<float>(_scene->getCanvasSize().second) / image_size.height;
×
1203
                
1204
                // Convert each point from default media coordinates to line-specific coordinates
NEW
1205
                for (auto const & point : line_points) {
×
NEW
1206
                    float x_canvas = point.x * default_xAspect;
×
NEW
1207
                    float y_canvas = point.y * default_yAspect;
×
NEW
1208
                    float x_converted = x_canvas / line_xAspect;
×
NEW
1209
                    float y_converted = y_canvas / line_yAspect;
×
NEW
1210
                    line_to_apply.push_back(Point2D<float>{x_converted, y_converted});
×
1211
                }
1212
            } else {
NEW
1213
                line_to_apply = Line2D(line_points);
×
1214
            }
1215
        } else {
NEW
1216
            line_to_apply = Line2D(line_points);
×
1217
        }
NEW
1218
    } else {
×
NEW
1219
        line_to_apply = Line2D(line_points);
×
1220
    }
1221

1222
    // Apply the line to all frames
1223
    int frames_processed = 0;
×
1224
    for (auto const & frame_time: frame_times) {
×
1225
        line_data->addAtTime(frame_time, line_to_apply, false);// Don't notify for each frame
×
1226
        frames_processed++;
×
1227
    }
1228

1229
    // Notify observers once at the end
1230
    line_data->notifyObservers();
×
1231

1232
    // Clear the line points after applying
1233
    _drawAllFramesSelectionWidget->clearLinePoints();
×
1234
    
1235
    // Clear the temporary line visualization
NEW
1236
    _scene->clearTemporaryLine();
×
1237

1238
    _scene->UpdateCanvas();
×
1239
}
×
1240

1241
std::vector<TimeFrameIndex> MediaLine_Widget::_getAllFrameTimes() {
×
1242
    std::vector<TimeFrameIndex> frame_times;
×
1243

1244
    // Get media data to determine total frame count
1245
    auto media_data = _data_manager->getData<MediaData>("media");
×
1246
    if (!media_data) {
×
1247
        std::cout << "No media data available" << std::endl;
×
1248
        return frame_times;
×
1249
    }
1250

1251
    int total_frames = media_data->getTotalFrameCount();
×
1252
    if (total_frames <= 0) {
×
1253
        std::cout << "Invalid frame count: " << total_frames << std::endl;
×
1254
        return frame_times;
×
1255
    }
1256

1257
    // Create TimeFrameIndex for each frame
1258
    frame_times.reserve(total_frames);
×
1259
    for (int i = 0; i < total_frames; ++i) {
×
1260
        frame_times.emplace_back(i);
×
1261
    }
1262

1263
    return frame_times;
1264
}
×
1265

1266
int MediaLine_Widget::_getSelectedLineIndexFromGroupSystem() const {
×
1267
    // Get the selected entities from the group system
1268
    auto selected_entities = _scene->getSelectedEntities();
×
1269
    
1270
    // If no entities are selected, return -1
1271
    if (selected_entities.empty()) {
×
1272
        return -1;
×
1273
    }
1274
    
1275
    // For now, just return the first selected entity as the line index
1276
    // EntityId for lines corresponds to their index in the line data
1277
    EntityId first_selected = *selected_entities.begin();
×
1278
    return static_cast<int>(first_selected);
×
1279
}
×
1280

NEW
1281
void MediaLine_Widget::_updateTemporaryLineFromWidget() {
×
NEW
1282
    if (_drawAllFramesSelectionWidget) {
×
NEW
1283
        auto current_points = _drawAllFramesSelectionWidget->getCurrentLinePoints();
×
NEW
1284
        _scene->updateTemporaryLine(current_points, ""); // Use default aspect ratios
×
NEW
1285
    }
×
NEW
1286
}
×
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