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

paulmthompson / WhiskerToolbox / 18008514883

25 Sep 2025 01:08PM UTC coverage: 68.869% (-0.05%) from 68.919%
18008514883

push

github

paulmthompson
dlc loader for csv files

92 of 102 new or added lines in 3 files covered. (90.2%)

680 existing lines in 10 files now uncovered.

41941 of 60900 relevant lines covered (68.87%)

1139.5 hits per line

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

86.81
/src/WhiskerToolbox/Analysis_Dashboard/Widgets/Common/BasePlotOpenGLWidget.cpp
1
#include "BasePlotOpenGLWidget.hpp"
2
#include "GenericViewAdapter.hpp"
3
#include "GroupManagementWidget/GroupManager.hpp"
4
#include "PlotInteractionController.hpp"
5
#include "Selection/LineSelectionHandler.hpp"
6
#include "Selection/NoneSelectionHandler.hpp"
7
#include "Selection/PointSelectionHandler.hpp"
8
#include "Selection/PolygonSelectionHandler.hpp"
9
#include "TooltipManager.hpp"
10
#include "widget_utilities.hpp"
11

12
#include <QApplication>
13
#include <QDebug>
14
#include <QKeyEvent>
15
#include <QMouseEvent>
16
#include <QWheelEvent>
17

18
BasePlotOpenGLWidget::BasePlotOpenGLWidget(QWidget * parent)
19✔
19
    : QOpenGLWidget(parent),
20
      _group_manager(nullptr),
19✔
21
      _point_size(8.0f),
19✔
22
      _line_width(2.0f),
19✔
23
      _tooltips_enabled(true),
19✔
24
      _opengl_resources_initialized(false),
19✔
25
      _pending_update(false) {
19✔
26

27
    // Set up widget properties
28
    setMouseTracking(true);
19✔
29
    setFocusPolicy(Qt::StrongFocus);
19✔
30

31
    // Initialize default selection handler
32
    _selection_callback = nullptr;
19✔
33
    createSelectionHandler(_selection_mode);
19✔
34

35
    // Set up OpenGL context with subclass-specified requirements
36
    auto [major, minor] = getRequiredOpenGLVersion();
19✔
37
    int samples = getRequiredSamples();
19✔
38

39
    QSurfaceFormat format;
19✔
40
    format.setVersion(major, minor);
19✔
41
    format.setProfile(QSurfaceFormat::CoreProfile);
19✔
42
    format.setSamples(samples);
19✔
43
    format.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
19✔
44
    format.setSwapInterval(1);
19✔
45
    setFormat(format);
19✔
46

47
    // Set up FPS limiter timer
48
    _fps_limiter_timer = new QTimer(this);
19✔
49
    _fps_limiter_timer->setSingleShot(true);
19✔
50
    _fps_limiter_timer->setInterval(33);// ~30 FPS
19✔
51
    connect(_fps_limiter_timer, &QTimer::timeout, this, [this]() {
19✔
52
        if (_pending_update) {
35✔
53
            _pending_update = false;
29✔
54
            update();
29✔
55
        }
56
    });
35✔
57

58
    // Initialize tooltip manager
59
    _tooltip_manager = std::make_unique<TooltipManager>(this);
19✔
60
    _tooltip_manager->setContentProvider([this](QPoint const & screen_pos) -> std::optional<QString> {
19✔
61
        return generateTooltipContent(screen_pos);
×
62
    });
63

64
    qDebug() << "BasePlotOpenGLWidget: Created base plot widget with OpenGL"
38✔
65
             << major << "." << minor << "and" << samples << "samples";
38✔
66
}
38✔
67

68
BasePlotOpenGLWidget::~BasePlotOpenGLWidget() = default;
19✔
69

70
void BasePlotOpenGLWidget::setGroupManager(GroupManager * group_manager) {
7✔
71
    _group_manager = group_manager;
7✔
72

73
    qDebug() << "BasePlotOpenGLWidget: Set group manager";
7✔
74
    
75
    // Enforce derived class implementation
76
    doSetGroupManager(group_manager);
7✔
77
}
7✔
78

79
void BasePlotOpenGLWidget::setPointSize(float point_size) {
1✔
80
    float new_point_size = std::max(1.0f, std::min(50.0f, point_size));
1✔
81
    if (new_point_size != _point_size) {
1✔
82
        _point_size = new_point_size;
1✔
83
        requestThrottledUpdate();
1✔
84
    }
85
}
1✔
86

87
void BasePlotOpenGLWidget::setTooltipsEnabled(bool enabled) {
1✔
88
    _tooltips_enabled = enabled;
1✔
89
    if (_tooltip_manager) {
1✔
90
        _tooltip_manager->setEnabled(enabled);
1✔
91
    }
92
}
1✔
93

94
QVector2D BasePlotOpenGLWidget::screenToWorld(QPoint const & screen_pos) const {
121✔
95
    // Update view state dimensions for accurate conversion
96
    _view_state.widget_width = width();
121✔
97
    _view_state.widget_height = height();
121✔
98

99
    auto world_point = ViewUtils::screenToWorld(_view_state, screen_pos.x(), screen_pos.y());
121✔
100
    return {world_point.x, world_point.y};
121✔
101
}
102

103
QPoint BasePlotOpenGLWidget::worldToScreen(float world_x, float world_y) const {
×
104
    // Update view state dimensions for accurate conversion
105
    _view_state.widget_width = width();
×
106
    _view_state.widget_height = height();
×
107

UNCOV
108
    auto screen_point = ViewUtils::worldToScreen(_view_state, world_x, world_y);
×
109
    return QPoint{static_cast<int>(screen_point.x), static_cast<int>(screen_point.y)};
×
110
}
111

112
void BasePlotOpenGLWidget::resetView() {
1✔
113
    // Ensure ViewState has current data bounds before resetting
114
    auto data_bounds = getDataBounds();
1✔
115
    _view_state.data_bounds = data_bounds;
1✔
116
    _view_state.data_bounds_valid = true;
1✔
117
    _view_state.widget_width = width();
1✔
118
    _view_state.widget_height = height();
1✔
119

120
    ViewUtils::resetView(_view_state);
1✔
121
    updateViewMatrices();
1✔
122
    requestThrottledUpdate();
1✔
123
}
1✔
124

125
void BasePlotOpenGLWidget::paintGL() {
104✔
126
    if (!initializeRendering()) {
104✔
UNCOV
127
        return;
×
128
    }
129

130
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
104✔
131

132
    renderBackground();
104✔
133
    renderData();// Pure virtual - implemented by subclasses
104✔
134
    renderOverlays();
104✔
135
    renderUI();
104✔
136
}
137

138
void BasePlotOpenGLWidget::initializeGL() {
19✔
139
    qDebug() << "BasePlotOpenGLWidget::initializeGL called";
19✔
140

141
    initializeOpenGLFunctions();
19✔
142

143
    // Validate that we got the requested OpenGL version
144
    if (!validateOpenGLContext()) {
19✔
UNCOV
145
        qWarning() << "BasePlotOpenGLWidget: OpenGL context validation failed";
×
146
        // Continue anyway, but subclasses should check capabilities
147
    }
148

149
    // Set up OpenGL state
150
    glClearColor(0.95f, 0.95f, 0.95f, 1.0f);
19✔
151
    glEnable(GL_BLEND);
19✔
152
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
19✔
153
    glEnable(GL_PROGRAM_POINT_SIZE);
19✔
154

155
    // Enable multisampling if available
156
    auto fmt = format();
19✔
157
    if (fmt.samples() > 1) {
19✔
UNCOV
158
        glEnable(GL_MULTISAMPLE);
×
159
    }
160

161
    _opengl_resources_initialized = true;
19✔
162

163
    // Create interaction controller if not already created
164
    if (!_interaction) {
19✔
165
        // This will be overridden by subclasses to provide their specific view adapter
166
        // For now, we'll delay creation until subclass is ready
167
    }
168

169
    updateViewMatrices();
19✔
170

171
    qDebug() << "BasePlotOpenGLWidget::initializeGL completed";
19✔
172
}
38✔
173

174
void BasePlotOpenGLWidget::resizeGL(int w, int h) {
44✔
175
    glViewport(0, 0, w, h);
44✔
176
    updateViewMatrices();
44✔
177
}
44✔
178

179
void BasePlotOpenGLWidget::mousePressEvent(QMouseEvent * event) {
20✔
180

181
    if (_tooltip_manager) {
20✔
182
        _tooltip_manager->setSuppressed(true);
20✔
183
    }
184

185
    if (_interaction) {
20✔
186
        _interaction->handleMousePress(event);
20✔
187
    }
188

189
    //if (!hasFocus()) {
190
    //    setFocus(Qt::MouseFocusReason);
191
    //}
192

193
    auto world_pos = screenToWorld(event->pos());
20✔
194

195
    std::visit([event, world_pos](auto & handler) {
40✔
196
        if (handler) {
20✔
197
            handler->mousePressEvent(event, world_pos);
20✔
198
        }
199
    },
20✔
200
               _selection_handler);
20✔
201

202
    requestThrottledUpdate();
20✔
203

204
    // Default behavior if interaction controller doesn't handle it
205
    QOpenGLWidget::mousePressEvent(event);
20✔
206
}
20✔
207

208
void BasePlotOpenGLWidget::mouseMoveEvent(QMouseEvent * event) {
9✔
209
    if (_interaction && _interaction->handleMouseMove(event)) {
9✔
UNCOV
210
        return;
×
211
    }
212

213
    // Update tooltip manager with mouse position
214
    if (_tooltip_manager) {
9✔
215
        _tooltip_manager->handleMouseMove(event->globalPos()); // Global Pos is DEPRECATED
9✔
216
    }
217

218
    // Emit world coordinates for other components to use
219
    auto world_pos = screenToWorld(event->pos());
9✔
220
    emit mouseWorldMoved(world_pos.x(), world_pos.y());
9✔
221

222
    std::visit([event, world_pos](auto & handler) {
18✔
223
        if (handler) {
9✔
224
            handler->mouseMoveEvent(event, world_pos);
9✔
225
        }
226
    },
9✔
227
               _selection_handler);
9✔
228

229
    QOpenGLWidget::mouseMoveEvent(event);
9✔
230
}
231

232
void BasePlotOpenGLWidget::mouseReleaseEvent(QMouseEvent * event) {
19✔
233
    // Re-enable tooltips after interaction
234
    if (_tooltip_manager) {
19✔
235
        _tooltip_manager->setSuppressed(false);
19✔
236
    }
237

238
    if (_interaction && _interaction->handleMouseRelease(event)) {
19✔
UNCOV
239
        return;
×
240
    }
241

242
    auto world_pos = screenToWorld(event->pos());
19✔
243

244
    std::visit([event, world_pos](auto & handler) {
38✔
245
        if (handler) {
19✔
246
            handler->mouseReleaseEvent(event, world_pos);
19✔
247
        }
248
    },
19✔
249
               _selection_handler);
19✔
250

251

252
    QOpenGLWidget::mouseReleaseEvent(event);
19✔
253
}
254

UNCOV
255
void BasePlotOpenGLWidget::wheelEvent(QWheelEvent * event) {
×
UNCOV
256
    if (_interaction && _interaction->handleWheel(event)) {
×
257
        return;
×
258
    }
259

UNCOV
260
    QOpenGLWidget::wheelEvent(event);
×
261
}
262

263
void BasePlotOpenGLWidget::leaveEvent(QEvent * event) {
1✔
264
    if (_interaction) {
1✔
265
        _interaction->handleLeave();
1✔
266
    }
267

268
    // Hide tooltip when mouse leaves widget
269
    if (_tooltip_manager) {
1✔
270
        _tooltip_manager->handleMouseLeave();
1✔
271
    }
272

273
    QOpenGLWidget::leaveEvent(event);
1✔
274
}
1✔
275

276
void BasePlotOpenGLWidget::handleKeyPress(QKeyEvent * event) {
2✔
277

278
    std::visit([event](auto & handler) {
4✔
279
        if (handler) {
2✔
280
            handler->keyPressEvent(event);
2✔
281
        }
282
    },
2✔
283
               _selection_handler);
2✔
284

285
    switch (event->key()) {
2✔
286
        case Qt::Key_R:
×
287
            // Reset view
UNCOV
288
            resetView();
×
UNCOV
289
            event->accept();
×
UNCOV
290
            break;
×
291
        default:
2✔
292
            // Let the widget handle it normally
293
            keyPressEvent(event);
2✔
294
            break;
2✔
295
    }
296
}
2✔
297

298
void BasePlotOpenGLWidget::setSelectionMode(SelectionMode mode) {
8✔
299
    if (_selection_mode != mode) {
8✔
300
        _selection_mode = mode;
8✔
301

302
        createSelectionHandler(mode);
8✔
303

304
        emit selectionModeChanged(mode);
8✔
305
    }
306
}
8✔
307

308

309
void BasePlotOpenGLWidget::createSelectionHandler(SelectionMode mode) {
27✔
310
    // Ensure OpenGL context is current for handlers that need OpenGL functions
311
    if (mode == SelectionMode::PolygonSelection || mode == SelectionMode::LineIntersection) {
27✔
312
        makeCurrent();
2✔
313
    }
314

315
    switch (mode) {
27✔
316
        case SelectionMode::None:
19✔
317
            _selection_handler = std::make_unique<NoneSelectionHandler>();
19✔
318
            break;
19✔
319
        case SelectionMode::PointSelection:
6✔
320
            _selection_handler = std::make_unique<PointSelectionHandler>(10.0f);// 10 pixel tolerance
6✔
321
            break;
6✔
322
        case SelectionMode::PolygonSelection:
2✔
323
            _selection_handler = std::make_unique<PolygonSelectionHandler>();
2✔
324
            break;
2✔
UNCOV
325
        case SelectionMode::LineIntersection:
×
UNCOV
326
            _selection_handler = std::make_unique<LineSelectionHandler>();
×
UNCOV
327
            break;
×
328
    }
329

330
    // Set up notification callback for the handler
331
    std::visit([this](auto & handler) {
54✔
332
        if (handler) {
27✔
333
            handler->setNotificationCallback(_selection_callback);
27✔
334
        }
335
    },
27✔
336
               _selection_handler);
27✔
337
}
27✔
338

339
void BasePlotOpenGLWidget::renderBackground() {
104✔
340
    // Default implementation - just clear
341
    // Subclasses can override for custom backgrounds
342
}
104✔
343

344
void BasePlotOpenGLWidget::renderOverlays() {
104✔
345
    auto context = createRenderingContext();
104✔
346
    auto mvp_matrix = context.projection_matrix * context.view_matrix * context.model_matrix;
104✔
347

348
    std::visit([mvp_matrix](auto & handler) {
208✔
349
        if (handler) {
104✔
350
            handler->render(mvp_matrix);
104✔
351
        }
352
    },
104✔
353
               _selection_handler);
104✔
354
    requestThrottledUpdate();
104✔
355
}
104✔
356

357
void BasePlotOpenGLWidget::renderUI() {
2✔
358
    // Default implementation for axes, labels, etc.
359
    // Subclasses can override for custom UI elements
360
}
2✔
361

362
std::optional<QString> BasePlotOpenGLWidget::generateTooltipContent(QPoint const & screen_pos) const {
×
363
    // Default implementation - subclasses should override to provide specific tooltip content
364
    Q_UNUSED(screen_pos)
UNCOV
365
    return std::nullopt;
×
366
}
367

368
RenderingContext BasePlotOpenGLWidget::createRenderingContext() const {
217✔
369
    RenderingContext context;
217✔
370
    context.model_matrix = _model_matrix;
217✔
371
    context.view_matrix = _view_matrix;
217✔
372
    context.projection_matrix = _projection_matrix;
217✔
373
    context.viewport_rect = QRect(0, 0, width(), height());
217✔
374

375
    // Calculate world bounds based on current view
376
    auto data_bounds = getDataBounds();
217✔
377
    float cx, cy, w_world, h_world;
217✔
378
    computeCameraWorldView(cx, cy, w_world, h_world);
217✔
379

380
    float half_w = w_world * 0.5f;
217✔
381
    float half_h = h_world * 0.5f;
217✔
382
    context.world_bounds = QRectF(cx - half_w, cy - half_h, w_world, h_world);
217✔
383

384
    return context;
434✔
385
}
386

387
void BasePlotOpenGLWidget::updateViewMatrices() {
77✔
388
    _model_matrix.setToIdentity();
77✔
389

390
    auto data_bounds = getDataBounds();
77✔
391

392
    // Update ViewState with current data bounds and widget dimensions
393
    _view_state.data_bounds = data_bounds;
77✔
394
    _view_state.data_bounds_valid = true;
77✔
395
    _view_state.widget_width = width();
77✔
396
    _view_state.widget_height = height();
77✔
397

398
    if (width() <= 0 || height() <= 0) {
77✔
UNCOV
399
        _view_matrix.setToIdentity();
×
UNCOV
400
        _projection_matrix.setToIdentity();
×
UNCOV
401
        return;
×
402
    }
403

404
    // Compute camera center and world-space visible extents
405
    float cx, cy, w_world, h_world;
77✔
406
    computeCameraWorldView(cx, cy, w_world, h_world);
77✔
407

408
    // View matrix encodes pan/zoom
409
    _view_matrix.setToIdentity();
77✔
410
    float aspect = static_cast<float>(width()) / static_cast<float>(std::max(1, height()));
77✔
411
    float scale_x = (w_world > 0.0f) ? ((2.0f * aspect) / w_world) : 1.0f;
77✔
412
    float scale_y = (h_world > 0.0f) ? (2.0f / h_world) : 1.0f;
77✔
413
    _view_matrix.scale(scale_x, scale_y, 1.0f);
77✔
414
    _view_matrix.translate(-cx, -cy, 0.0f);
77✔
415

416
    // Projection handles aspect only
417
    _projection_matrix.setToIdentity();
77✔
418
    float left = -aspect;
77✔
419
    float right = aspect;
77✔
420
    float bottom = -1.0f;
77✔
421
    float top = 1.0f;
77✔
422
    _projection_matrix.ortho(left, right, bottom, top, -1.0f, 1.0f);
77✔
423

424
    // Emit the current visible world bounds
425
    float half_w = w_world * 0.5f;
77✔
426
    float half_h = h_world * 0.5f;
77✔
427
    BoundingBox view_bounds(cx - half_w, cy - half_h, cx + half_w, cy + half_h);
77✔
428
    emit viewBoundsChanged(view_bounds);
77✔
429
}
430

431
void BasePlotOpenGLWidget::requestThrottledUpdate() {
172✔
432
    if (!_fps_limiter_timer->isActive()) {
172✔
433
        update();
52✔
434
        emit highlightStateChanged();
52✔
435
        _fps_limiter_timer->start();
52✔
436
    } else {
437
        _pending_update = true;
120✔
438
    }
439
}
172✔
440

441
bool BasePlotOpenGLWidget::initializeRendering() {
104✔
442
    if (!_opengl_resources_initialized) {
104✔
UNCOV
443
        qDebug() << "BasePlotOpenGLWidget::initializeRendering: OpenGL resources not initialized yet";
×
UNCOV
444
        return false;
×
445
    }
446

447
    if (!context() || !context()->isValid()) {
104✔
UNCOV
448
        qWarning() << "BasePlotOpenGLWidget::initializeRendering: Invalid OpenGL context";
×
UNCOV
449
        return false;
×
450
    }
451

452
    return true;
104✔
453
}
454

455
void BasePlotOpenGLWidget::computeCameraWorldView(float & center_x, float & center_y,
294✔
456
                                                  float & world_width, float & world_height) const {
457
    ViewUtils::computeCameraWorldView(_view_state, center_x, center_y, world_width, world_height);
294✔
458
}
294✔
459

460
bool BasePlotOpenGLWidget::validateOpenGLContext() const {
19✔
461
    if (!context() || !context()->isValid()) {
19✔
UNCOV
462
        qWarning() << "BasePlotOpenGLWidget: OpenGL context is invalid";
×
UNCOV
463
        return false;
×
464
    }
465

466
    auto fmt = format();
19✔
467
    auto [req_major, req_minor] = getRequiredOpenGLVersion();
19✔
468

469
    qDebug() << "BasePlotOpenGLWidget: Requested OpenGL" << req_major << "." << req_minor;
19✔
470
    qDebug() << "BasePlotOpenGLWidget: Actual OpenGL" << fmt.majorVersion() << "." << fmt.minorVersion();
19✔
471

472
    // Check if we got at least the requested version
473
    if (fmt.majorVersion() < req_major ||
38✔
474
        (fmt.majorVersion() == req_major && fmt.minorVersion() < req_minor)) {
19✔
UNCOV
475
        qWarning() << "BasePlotOpenGLWidget: Requested OpenGL" << req_major << "." << req_minor
×
UNCOV
476
                   << "but got" << fmt.majorVersion() << "." << fmt.minorVersion();
×
UNCOV
477
        return false;
×
478
    }
479

480
    // Check profile
481
    if (fmt.profile() != QSurfaceFormat::CoreProfile) {
19✔
UNCOV
482
        qWarning() << "BasePlotOpenGLWidget: Expected Core profile but got" << fmt.profile();
×
UNCOV
483
        return false;
×
484
    }
485

486
    qDebug() << "BasePlotOpenGLWidget: OpenGL context validation successful";
19✔
487
    return true;
19✔
488
}
19✔
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