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

paulmthompson / WhiskerToolbox / 18914877332

29 Oct 2025 03:47PM UTC coverage: 73.148% (+0.1%) from 73.01%
18914877332

push

github

paulmthompson
added UI for interval peak detection

56798 of 77648 relevant lines covered (73.15%)

44492.79 hits per line

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

40.06
/src/WhiskerToolbox/Media_Widget/Media_Widget.cpp
1
#include "Media_Widget.hpp"
2
#include "ui_Media_Widget.h"
3

4
#include "Collapsible_Widget/Section.hpp"
5
#include "CoreGeometry/ImageSize.hpp"
6
#include "DataManager/DataManager.hpp"
7
#include "DataManager/DigitalTimeSeries/Digital_Interval_Series.hpp"
8
#include "DataManager/Lines/Line_Data.hpp"
9
#include "DataManager/Masks/Mask_Data.hpp"
10
#include "DataManager/Points/Point_Data.hpp"
11
#include "Media_Widget/MediaInterval_Widget/MediaInterval_Widget.hpp"
12
#include "Media_Widget/MediaLine_Widget/MediaLine_Widget.hpp"
13
#include "Media_Widget/MediaMask_Widget/MediaMask_Widget.hpp"
14
#include "Media_Widget/MediaPoint_Widget/MediaPoint_Widget.hpp"
15
#include "Media_Widget/MediaProcessing_Widget/MediaProcessing_Widget.hpp"
16
#include "Media_Widget/MediaTensor_Widget/MediaTensor_Widget.hpp"
17
#include "Media_Widget/MediaText_Widget/MediaText_Widget.hpp"
18
#include "Media_Window/Media_Window.hpp"
19

20
//https://stackoverflow.com/questions/72533139/libtorch-errors-when-used-with-qt-opencv-and-point-cloud-library
21
#undef slots
22
#include "DataManager/Tensors/Tensor_Data.hpp"
23
#define slots Q_SLOTS
24

25
#include <QApplication>
26
#include <QGraphicsView>
27
#include <QMouseEvent>
28
#include <QResizeEvent>
29
#include <QScrollBar>
30
#include <QSplitter>
31
#include <QTimer>
32
#include <QVBoxLayout>
33
#include <QWheelEvent>
34

35
#include <algorithm>
36

37
Media_Widget::Media_Widget(QWidget * parent)
3✔
38
    : QWidget(parent),
39
      ui(new Ui::Media_Widget) {
3✔
40
    ui->setupUi(this);
3✔
41

42
    // Configure splitter behavior
43
    ui->splitter->setStretchFactor(0, 0);// Left panel (scroll area) doesn't stretch
3✔
44
    ui->splitter->setStretchFactor(1, 1);// Right panel (graphics view) stretches
3✔
45

46
    // Set initial sizes: 250px for left panel, rest for canvas (reduced from 350px)
47
    ui->splitter->setSizes({250, 513});
3✔
48

49
    // Set collapsible behavior
50
    ui->splitter->setCollapsible(0, false);// Prevent left panel from collapsing
3✔
51
    ui->splitter->setCollapsible(1, false);// Prevent canvas from collapsing
3✔
52

53
    // Connect splitter moved signal to update canvas size
54
    connect(ui->splitter, &QSplitter::splitterMoved, this, &Media_Widget::_updateCanvasSize);
3✔
55

56
    // Install event filter on graphics view viewport for wheel zoom
57
    if (ui->graphicsView && ui->graphicsView->viewport()) {
3✔
58
        ui->graphicsView->viewport()->installEventFilter(this);
3✔
59
        ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorViewCenter);
3✔
60
        ui->graphicsView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
3✔
61
    }
62

63
    // Create text overlay section
64
    _text_section = new Section(this, "Text Overlays");
3✔
65
    _text_widget = new MediaText_Widget(this);
3✔
66
    _text_section->setContentLayout(*new QVBoxLayout());
3✔
67
    _text_section->layout()->addWidget(_text_widget);
3✔
68
    _text_section->autoSetContentLayout();
3✔
69

70
    // Add text section to the vertical layout in scroll area
71
    // Insert before the feature table widget (at index 0, after the Zoom button)
72
    ui->verticalLayout->insertWidget(1, _text_section);
3✔
73

74
    connect(ui->feature_table_widget, &Feature_Table_Widget::featureSelected, this, &Media_Widget::_featureSelected);
3✔
75

76
    connect(ui->feature_table_widget, &Feature_Table_Widget::addFeature, this, [this](QString const & feature) {
3✔
77
        Media_Widget::_addFeatureToDisplay(feature, true);
×
78
    });
×
79

80
    connect(ui->feature_table_widget, &Feature_Table_Widget::removeFeature, this, [this](QString const & feature) {
3✔
81
        Media_Widget::_addFeatureToDisplay(feature, false);
×
82
    });
×
83

84
    // Ensure the feature table and other widgets are properly sized on startup
85
    QTimer::singleShot(0, this, [this]() {
3✔
86
        int scrollAreaWidth = ui->scrollArea->width();
3✔
87
        ui->feature_table_widget->setFixedWidth(scrollAreaWidth - 10);
3✔
88

89
        // Call the update function to size everything properly
90
        _updateCanvasSize();
3✔
91

92
        // Initialize the stacked widget with proper sizing
93
        ui->stackedWidget->setFixedWidth(scrollAreaWidth - 10);
3✔
94
    });
3✔
95
}
3✔
96

97
Media_Widget::~Media_Widget() {
3✔
98
    // Proactively hide stacked pages while _scene is still alive so any hideEvent
99
    // handlers that interact with _scene do so safely before _scene is destroyed.
100
    if (ui && ui->stackedWidget) {
3✔
101
        for (int i = 0; i < ui->stackedWidget->count(); ++i) {
24✔
102
            QWidget * w = ui->stackedWidget->widget(i);
21✔
103
            if (w && w->isVisible()) {
21✔
104
                w->hide();
2✔
105
            }
106
        }
107
    }
108

109
    // Ensure hover circle is cleared before scene destruction
110
    if (_scene) {
3✔
111
        _scene->setShowHoverCircle(false);
3✔
112
    }
113

114
    delete ui;
3✔
115
}
3✔
116

117
void Media_Widget::updateMedia() {
2✔
118

119
    ui->graphicsView->setScene(_scene.get());
2✔
120
    ui->graphicsView->show();
2✔
121

122
    _updateCanvasSize();
2✔
123
}
2✔
124

125
void Media_Widget::setDataManager(std::shared_ptr<DataManager> data_manager) {
3✔
126
    _data_manager = std::move(data_manager);
3✔
127

128
    // Create the Media_Window now that we have a DataManager
129
    _createMediaWindow();
3✔
130
    _createOptions();
3✔
131

132
    ui->feature_table_widget->setColumns({"Feature", "Enabled", "Type"});
15✔
133
    ui->feature_table_widget->setTypeFilter({DM_DataType::Line, DM_DataType::Mask, DM_DataType::Points, DM_DataType::DigitalInterval, DM_DataType::Tensor, DM_DataType::Video, DM_DataType::Images});
9✔
134
    ui->feature_table_widget->setDataManager(_data_manager);
3✔
135
    ui->feature_table_widget->populateTable();
3✔
136

137
    ui->stackedWidget->addWidget(new MediaPoint_Widget(_data_manager, _scene.get()));
3✔
138
    ui->stackedWidget->addWidget(new MediaLine_Widget(_data_manager, _scene.get()));
3✔
139
    ui->stackedWidget->addWidget(new MediaMask_Widget(_data_manager, _scene.get()));
3✔
140
    ui->stackedWidget->addWidget(new MediaInterval_Widget(_data_manager, _scene.get()));
3✔
141
    ui->stackedWidget->addWidget(new MediaTensor_Widget(_data_manager, _scene.get()));
3✔
142

143
    // Create and store reference to MediaProcessing_Widget
144
    _processing_widget = new MediaProcessing_Widget(_data_manager, _scene.get());
3✔
145
    ui->stackedWidget->addWidget(_processing_widget);
3✔
146

147
    // Connect text widget to scene if both are available
148
    _connectTextWidgetToScene();
3✔
149

150
    // Ensure all widgets in the stacked widget are properly sized
151
    QTimer::singleShot(100, this, [this]() {
3✔
152
        int scrollAreaWidth = ui->scrollArea->width();
×
153
        for (int i = 0; i < ui->stackedWidget->count(); ++i) {
×
154
            QWidget * widget = ui->stackedWidget->widget(i);
×
155
            if (widget) {
×
156
                widget->setFixedWidth(scrollAreaWidth - 10);
×
157
                widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
×
158

159
                // If this is a MediaProcessing_Widget, make sure it fills its container
160
                auto processingWidget = qobject_cast<MediaProcessing_Widget *>(widget);
×
161
                if (processingWidget) {
×
162
                    processingWidget->setMinimumWidth(scrollAreaWidth - 10);
×
163
                    processingWidget->adjustSize();
×
164
                }
165
            }
166
        }
167
        _updateCanvasSize();
×
168
    });
×
169

170
    _data_manager->addObserver([this]() {
3✔
171
        _createOptions();
1✔
172
    });
1✔
173
}
6✔
174

175
void Media_Widget::_createOptions() {
4✔
176

177
    //Setup Media Data
178
    auto media_keys = _data_manager->getKeys<MediaData>();
4✔
179
    for (auto const & media_key: media_keys) {
8✔
180
        auto opts = _scene->getMediaConfig(media_key);
4✔
181
        if (opts.has_value()) continue;
4✔
182

183
        _scene->addMediaDataToScene(media_key);
3✔
184
    }
185

186
    // Setup line data
187
    auto line_keys = _data_manager->getKeys<LineData>();
4✔
188
    for (auto const & line_key: line_keys) {
4✔
189
        auto opts = _scene->getLineConfig(line_key);
×
190
        if (opts.has_value()) continue;
×
191

192
        _scene->addLineDataToScene(line_key);
×
193
    }
194

195
    // Setup mask data
196
    auto mask_keys = _data_manager->getKeys<MaskData>();
4✔
197
    for (auto const & mask_key: mask_keys) {
7✔
198
        auto opts = _scene->getMaskConfig(mask_key);
3✔
199
        if (opts.has_value()) continue;
3✔
200

201
        _scene->addMaskDataToScene(mask_key);
3✔
202
    }
203

204
    // Setup point data
205
    auto point_keys = _data_manager->getKeys<PointData>();
4✔
206
    for (auto const & point_key: point_keys) {
4✔
207
        auto opts = _scene->getPointConfig(point_key);
×
208
        if (opts.has_value()) continue;
×
209

210
        _scene->addPointDataToScene(point_key);
×
211
    }
212

213
    // Setup digital interval data
214
    auto interval_keys = _data_manager->getKeys<DigitalIntervalSeries>();
4✔
215
    for (auto const & interval_key: interval_keys) {
4✔
216
        auto opts = _scene->getIntervalConfig(interval_key);
×
217
        if (opts.has_value()) continue;
×
218

219
        _scene->addDigitalIntervalSeries(interval_key);
×
220
    }
221

222
    // Setup tensor data
223
    auto tensor_keys = _data_manager->getKeys<TensorData>();
4✔
224
    for (auto const & tensor_key: tensor_keys) {
4✔
225
        auto opts = _scene->getTensorConfig(tensor_key);
×
226
        if (opts.has_value()) continue;
×
227

228
        _scene->addTensorDataToScene(tensor_key);
×
229
    }
230
}
8✔
231

232
void Media_Widget::_connectTextWidgetToScene() {
6✔
233
    if (_scene && _text_widget) {
6✔
234
        _scene->setTextWidget(_text_widget);
6✔
235

236
        // Connect text widget signals to update canvas when overlays change
237
        connect(_text_widget, &MediaText_Widget::textOverlayAdded, _scene.get(), &Media_Window::UpdateCanvas);
6✔
238
        connect(_text_widget, &MediaText_Widget::textOverlayRemoved, _scene.get(), &Media_Window::UpdateCanvas);
6✔
239
        connect(_text_widget, &MediaText_Widget::textOverlayUpdated, _scene.get(), &Media_Window::UpdateCanvas);
6✔
240
        connect(_text_widget, &MediaText_Widget::textOverlaysCleared, _scene.get(), &Media_Window::UpdateCanvas);
6✔
241
    }
242
}
6✔
243

244
void Media_Widget::_featureSelected(QString const & feature) {
3✔
245
    auto const type = _data_manager->getType(feature.toStdString());
3✔
246
    auto key = feature.toStdString();
3✔
247

248
    if (type == DM_DataType::Points) {
3✔
249

250
        int const stacked_widget_index = 1;
×
251

252
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
×
253
        auto point_widget = dynamic_cast<MediaPoint_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
×
254
        point_widget->setActiveKey(key);
×
255

256
    } else if (type == DM_DataType::Line) {
3✔
257

258
        int const stacked_widget_index = 2;
×
259

260
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
×
261
        auto line_widget = dynamic_cast<MediaLine_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
×
262
        line_widget->setActiveKey(key);
×
263

264
    } else if (type == DM_DataType::Mask) {
3✔
265

266
        int const stacked_widget_index = 3;
3✔
267

268
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
3✔
269
        auto mask_widget = dynamic_cast<MediaMask_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
3✔
270
        mask_widget->setActiveKey(key);
3✔
271

272

273
    } else if (type == DM_DataType::DigitalInterval) {
×
274
        int const stacked_widget_index = 4;
×
275

276
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
×
277
        auto interval_widget = dynamic_cast<MediaInterval_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
×
278
        interval_widget->setActiveKey(key);
×
279

280
    } else if (type == DM_DataType::Tensor) {
×
281
        int const stacked_widget_index = 5;
×
282

283
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
×
284
        auto tensor_widget = dynamic_cast<MediaTensor_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
×
285
        tensor_widget->setActiveKey(key);
×
286
    } else if (type == DM_DataType::Video) {
×
287
        int const stacked_widget_index = 6;
×
288

289
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
×
290
        auto processing_widget = dynamic_cast<MediaProcessing_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
×
291
        processing_widget->setActiveKey(key);
×
292

293
        // Do NOT set as active media key or update canvas - only show controls for configuration
294
        // The media will only be displayed when it's enabled via the checkbox
295

296
    } else if (type == DM_DataType::Images) {
×
297
        int const stacked_widget_index = 6;
×
298

299
        ui->stackedWidget->setCurrentIndex(stacked_widget_index);
×
300
        auto processing_widget = dynamic_cast<MediaProcessing_Widget *>(ui->stackedWidget->widget(stacked_widget_index));
×
301
        processing_widget->setActiveKey(key);
×
302

303
        // Do NOT set as active media key or update canvas - only show controls for configuration
304
        // The media will only be displayed when it's enabled via the checkbox
305
    } else {
306
        ui->stackedWidget->setCurrentIndex(0);
×
307
        std::cout << "Unsupported feature type" << std::endl;
×
308
    }
309
}
6✔
310

311
void Media_Widget::resizeEvent(QResizeEvent * event) {
2✔
312
    QWidget::resizeEvent(event);
2✔
313
    // When user has zoomed, avoid rescaling scene contents destructively; just adjust scene rect
314
    if (_user_zoom_active) {
2✔
315
        if (_scene) {
×
316
            auto size = ui->graphicsView->size();
×
317
            _scene->setSceneRect(0, 0, size.width(), size.height());
×
318
        }
319
    } else {
320
        _updateCanvasSize();
2✔
321
    }
322
}
2✔
323

324
void Media_Widget::_updateCanvasSize() {
7✔
325
    if (_scene) {
7✔
326
        int width = ui->graphicsView->width();
7✔
327
        int height = ui->graphicsView->height();
7✔
328

329
        _scene->setCanvasSize(
7✔
330
                ImageSize{width, height});
331
        _scene->UpdateCanvas();
7✔
332

333
        // Ensure the view fits the scene properly
334
        ui->graphicsView->setSceneRect(0, 0, width, height);
7✔
335
        if (!_user_zoom_active) {
7✔
336
            ui->graphicsView->resetTransform();
7✔
337
            _current_zoom = 1.0;
7✔
338
        }
339
        // Fit disabled: we manage zoom manually now
340
        // Update left panel sizing
341
        int scrollAreaWidth = ui->scrollArea->width();
7✔
342
        int featureTableWidth = scrollAreaWidth - 10;
7✔
343
        ui->feature_table_widget->setFixedWidth(featureTableWidth);
7✔
344
        ui->stackedWidget->setFixedWidth(featureTableWidth);
7✔
345
        for (int i = 0; i < ui->stackedWidget->count(); ++i) {
56✔
346
            QWidget * widget = ui->stackedWidget->widget(i);
49✔
347
            if (widget) {
49✔
348
                widget->setFixedWidth(featureTableWidth);
49✔
349
            }
350
        }
351
    }
352
}
7✔
353

354
void Media_Widget::_addFeatureToDisplay(QString const & feature, bool enabled) {
×
355
    std::cout << "Feature: " << feature.toStdString() << std::endl;
×
356

357
    auto const feature_key = feature.toStdString();
×
358
    auto const type = _data_manager->getType(feature_key);
×
359

360
    if (type == DM_DataType::Line) {
×
361
        auto opts = _scene->getLineConfig(feature_key);
×
362
        if (!opts.has_value()) {
×
363
            std::cerr << "Table feature key "
364
                      << feature_key
365
                      << " not found in Media_Window Display Options"
×
366
                      << std::endl;
×
367
            return;
×
368
        }
369
        opts.value()->is_visible = enabled;
×
370
    } else if (type == DM_DataType::Mask) {
×
371
        auto opts = _scene->getMaskConfig(feature_key);
×
372
        if (!opts.has_value()) {
×
373
            std::cerr << "Table feature key "
374
                      << feature_key
375
                      << " not found in Media_Window Display Options"
×
376
                      << std::endl;
×
377
            return;
×
378
        }
379
        opts.value()->is_visible = enabled;
×
380
    } else if (type == DM_DataType::Points) {
×
381
        auto opts = _scene->getPointConfig(feature_key);
×
382
        if (!opts.has_value()) {
×
383
            std::cerr << "Table feature key "
384
                      << feature_key
385
                      << " not found in Media_Window Display Options"
×
386
                      << std::endl;
×
387
            return;
×
388
        }
389
        opts.value()->is_visible = enabled;
×
390
    } else if (type == DM_DataType::DigitalInterval) {
×
391
        auto opts = _scene->getIntervalConfig(feature_key);
×
392
        if (!opts.has_value()) {
×
393
            std::cerr << "Table feature key "
394
                      << feature_key
395
                      << " not found in Media_Window Display Options"
×
396
                      << std::endl;
×
397
            return;
×
398
        }
399
        opts.value()->is_visible = enabled;
×
400
    } else if (type == DM_DataType::Tensor) {
×
401
        auto opts = _scene->getTensorConfig(feature_key);
×
402
        if (!opts.has_value()) {
×
403
            std::cerr << "Table feature key "
404
                      << feature_key
405
                      << " not found in Media_Window Display Options"
×
406
                      << std::endl;
×
407
            return;
×
408
        }
409

410
        if (enabled) {
×
411
            std::cout << "Enabling tensor data in scene" << std::endl;
×
412
            opts.value()->is_visible = true;
×
413
        } else {
414
            std::cout << "Disabling tensor data from scene" << std::endl;
×
415
            opts.value()->is_visible = false;
×
416
        }
417
    } else if (type == DM_DataType::Video || type == DM_DataType::Images) {
×
418
        auto opts = _scene->getMediaConfig(feature_key);
×
419
        if (!opts.has_value()) {
×
420
            std::cerr << "Table feature key "
421
                      << feature_key
422
                      << " not found in Media_Window Display Options"
×
423
                      << std::endl;
×
424
            return;
×
425
        }
426
        if (enabled) {
×
427
            std::cout << "Enabling media data in scene" << std::endl;
×
428
            opts.value()->is_visible = true;
×
429

430
            // This ensures new media is loaded from disk
431
            // Before the update
432
            auto current_time = _data_manager->getCurrentTime();
×
433
            LoadFrame(current_time);
×
434

435
        } else {
436
            std::cout << "Disabling media data from scene" << std::endl;
×
437
            opts.value()->is_visible = false;
×
438
        }
439
    } else {
×
440
        std::cout << "Feature type " << convert_data_type_to_string(type) << " not supported" << std::endl;
×
441
    }
442
    _scene->UpdateCanvas();
×
443

444
    if (enabled) {
×
445
        _callback_ids[feature_key].push_back(_data_manager->addCallbackToData(feature_key, [this]() {
×
446
            _scene->UpdateCanvas();
×
447
        }));
×
448
    } else {
449
        for (auto callback_id: _callback_ids[feature_key]) {
×
450
            _data_manager->removeCallbackFromData(feature_key, callback_id);
×
451
        }
452
        _callback_ids[feature_key].clear();
×
453
    }
454
}
×
455

456
void Media_Widget::setFeatureColor(std::string const & feature, std::string const & hex_color) {
×
457
    auto const type = _data_manager->getType(feature);
×
458

459
    if (type == DM_DataType::Line) {
×
460
        auto opts = _scene->getLineConfig(feature);
×
461
        if (opts.has_value()) {
×
462
            opts.value()->hex_color = hex_color;
×
463
        }
464
    } else if (type == DM_DataType::Mask) {
×
465
        auto opts = _scene->getMaskConfig(feature);
×
466
        if (opts.has_value()) {
×
467
            opts.value()->hex_color = hex_color;
×
468
        }
469
    } else if (type == DM_DataType::Points) {
×
470
        auto opts = _scene->getPointConfig(feature);
×
471
        if (opts.has_value()) {
×
472
            opts.value()->hex_color = hex_color;
×
473
        }
474
    } else if (type == DM_DataType::DigitalInterval) {
×
475
        auto opts = _scene->getIntervalConfig(feature);
×
476
        if (opts.has_value()) {
×
477
            opts.value()->hex_color = hex_color;
×
478
        }
479
    } else if (type == DM_DataType::Tensor) {
×
480
        auto opts = _scene->getTensorConfig(feature);
×
481
        if (opts.has_value()) {
×
482
            opts.value()->hex_color = hex_color;
×
483
        }
484
    }
485

486
    _scene->UpdateCanvas();
×
487
}
×
488

489
void Media_Widget::LoadFrame(int frame_id) {
×
490
    if (_scene) {
×
491
        _scene->LoadFrame(frame_id);
×
492
    }
493
    int currentIndex = ui->stackedWidget->currentIndex();
×
494
    if (currentIndex > 0) {
×
495
        auto currentWidget = ui->stackedWidget->currentWidget();
×
496
        auto lineWidget = dynamic_cast<MediaLine_Widget *>(currentWidget);
×
497
        if (lineWidget) {
×
498
            lineWidget->LoadFrame(frame_id);
×
499
        }
500
    }
501
}
×
502

503
// Zoom API implementations
504
void Media_Widget::zoomIn() { _applyZoom(_zoom_step, false); }
×
505
void Media_Widget::zoomOut() { _applyZoom(1.0 / _zoom_step, false); }
×
506
void Media_Widget::resetZoom() {
×
507
    if (!ui->graphicsView) return;
×
508
    ui->graphicsView->resetTransform();
×
509
    _current_zoom = 1.0;
×
510
    _user_zoom_active = false;
×
511
}
512

513
void Media_Widget::_applyZoom(double factor, bool anchor_under_mouse) {
×
514
    if (!ui->graphicsView) return;
×
515
    double new_zoom = _current_zoom * factor;
×
516
    new_zoom = std::clamp(new_zoom, _min_zoom, _max_zoom);
×
517
    factor = new_zoom / _current_zoom;// Adjust factor if clamped
×
518
    if (qFuzzyCompare(factor, 1.0)) return;
×
519
    if (anchor_under_mouse) {
×
520
        ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
×
521
    } else {
522
        ui->graphicsView->setTransformationAnchor(QGraphicsView::AnchorViewCenter);
×
523
    }
524
    ui->graphicsView->scale(factor, factor);
×
525
    _current_zoom = new_zoom;
×
526
    _user_zoom_active = (_current_zoom != 1.0);
×
527
}
528

529
bool Media_Widget::eventFilter(QObject * watched, QEvent * event) {
28✔
530
    if (watched == ui->graphicsView->viewport()) {
28✔
531
        // Handle wheel events for zoom
532
        if (event->type() == QEvent::Wheel) {
28✔
533
            auto * wheelEvent = dynamic_cast<QWheelEvent *>(event);
×
534
            double angle = wheelEvent->angleDelta().y();
×
535
            if (angle > 0) {
×
536
                _applyZoom(_zoom_step, true);
×
537
            } else if (angle < 0) {
×
538
                _applyZoom(1.0 / _zoom_step, true);
×
539
            }
540
            wheelEvent->accept();
×
541
            return true;
×
542
        }
543
        
544
        // Handle mouse events for shift+drag panning
545
        if (event->type() == QEvent::MouseButtonPress) {
28✔
546
            auto * mouseEvent = dynamic_cast<QMouseEvent *>(event);
×
547
            if (mouseEvent->button() == Qt::LeftButton && mouseEvent->modifiers() & Qt::ShiftModifier) {
×
548
                _is_panning = true;
×
549
                _last_pan_point = mouseEvent->pos();
×
550
                ui->graphicsView->viewport()->setCursor(Qt::ClosedHandCursor);
×
551
                mouseEvent->accept();
×
552
                return true; // Consume the event to prevent it from reaching Media_Window
×
553
            }
554
        }
555
        
556
        if (event->type() == QEvent::MouseMove && _is_panning) {
28✔
557
            auto * mouseEvent = dynamic_cast<QMouseEvent *>(event);
×
558
            QPoint delta = mouseEvent->pos() - _last_pan_point;
×
559
            _last_pan_point = mouseEvent->pos();
×
560
            
561
            // Apply panning by translating the view
562
            ui->graphicsView->horizontalScrollBar()->setValue(
×
563
                ui->graphicsView->horizontalScrollBar()->value() - delta.x());
×
564
            ui->graphicsView->verticalScrollBar()->setValue(
×
565
                ui->graphicsView->verticalScrollBar()->value() - delta.y());
×
566
            
567
            mouseEvent->accept();
×
568
            return true; // Consume the event
×
569
        }
570
        
571
        if (event->type() == QEvent::MouseButtonRelease) {
28✔
572
            auto * mouseEvent = dynamic_cast<QMouseEvent *>(event);
×
573
            if (mouseEvent->button() == Qt::LeftButton && _is_panning) {
×
574
                _is_panning = false;
×
575
                ui->graphicsView->viewport()->setCursor(Qt::ArrowCursor);
×
576
                mouseEvent->accept();
×
577
                return true; // Consume the event
×
578
            }
579
        }
580
    }
581
    return QWidget::eventFilter(watched, event);
28✔
582
}
583

584
void Media_Widget::_createMediaWindow() {
3✔
585
    if (_data_manager) {
3✔
586
        _scene = std::make_unique<Media_Window>(_data_manager, this);
3✔
587

588
        // Set parent widget reference for accessing enabled media keys
589
        _scene->setParentWidget(this);
3✔
590

591
        _connectTextWidgetToScene();
3✔
592
    }
593
}
3✔
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