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

paulmthompson / WhiskerToolbox / 18040375504

26 Sep 2025 02:15PM UTC coverage: 69.77% (+1.2%) from 68.577%
18040375504

push

github

paulmthompson
line selection works across widgets

5 of 20 new or added lines in 3 files covered. (25.0%)

463 existing lines in 11 files now uncovered.

42945 of 61552 relevant lines covered (69.77%)

1128.93 hits per line

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

71.28
/src/WhiskerToolbox/Analysis_Dashboard/Widgets/SpatialOverlayPlotWidget/SpatialOverlayPlotWidget.cpp
1
#include "SpatialOverlayPlotWidget.hpp"
2

3
#include "DataManager/DataManager.hpp"
4
#include "DataManager/Points/Point_Data.hpp"
5
#include "DataManager/TimeFrame/TimeFrame.hpp"
6
#include "SpatialOverlayOpenGLWidget.hpp"   
7

8

9
#include <QDebug>
10
#include <QGraphicsProxyWidget>
11
#include <QGraphicsSceneResizeEvent>
12
#include <QKeyEvent>
13
#include <QMouseEvent>
14
#include <QOpenGLBuffer>
15
#include <QOpenGLShaderProgram>
16
#include <QPaintEvent>
17
#include <QPainter>
18
#include <QPen>
19
#include <QTimer>
20

21
#include <cmath>
22

23
SpatialOverlayPlotWidget::SpatialOverlayPlotWidget(QGraphicsItem * parent)
14✔
24
    : AbstractPlotWidget(parent),
25
      _opengl_widget(nullptr),
14✔
26
      _proxy_widget(nullptr) {
14✔
27

28
    qDebug() << "SpatialOverlayPlotWidget: Constructor called";
14✔
29
    setPlotTitle("Spatial Overlay Plot");
14✔
30
    setupOpenGLWidget();
14✔
31
    qDebug() << "SpatialOverlayPlotWidget: Constructor completed, OpenGL widget:" << (_opengl_widget != nullptr);
14✔
32
}
14✔
33

34
SpatialOverlayPlotWidget::~SpatialOverlayPlotWidget() {
26✔
35
    // The _opengl_widget is owned by _proxy_widget and will be automatically deleted
36
    // when _proxy_widget is destroyed as a child of this graphics item.
37
    // Set our pointer to nullptr to avoid any potential issues.
38
    _opengl_widget = nullptr;
14✔
39
}
26✔
40

41
QString SpatialOverlayPlotWidget::getPlotType() const {
1✔
42
    return "Spatial Overlay Plot";
1✔
43
}
44

45
void SpatialOverlayPlotWidget::setDataKeys(QStringList const & point_data_keys,
3✔
46
                                           QStringList const & mask_data_keys,
47
                                           QStringList const & line_data_keys) {
48
    _point_data_keys = point_data_keys;
3✔
49
    _mask_data_keys = mask_data_keys;
3✔
50
    _line_data_keys = line_data_keys;
3✔
51
    updateVisualization();
3✔
52
}
3✔
53

54
void SpatialOverlayPlotWidget::paint(QPainter * painter, QStyleOptionGraphicsItem const * option, QWidget * widget) {
6✔
55
    Q_UNUSED(option)
56
    Q_UNUSED(widget)
57

58
    if (isFrameAndTitleVisible()) {
6✔
59
        // Draw frame around the plot
60
        QRectF rect = boundingRect();
6✔
61

62
        QPen border_pen;
6✔
63
        if (isSelected()) {
6✔
UNCOV
64
            border_pen.setColor(QColor(0, 120, 200));
×
UNCOV
65
            border_pen.setWidth(2);
×
66
        } else {
67
            border_pen.setColor(QColor(100, 100, 100));
6✔
68
            border_pen.setWidth(1);
6✔
69
        }
70
        painter->setPen(border_pen);
6✔
71
        painter->drawRect(rect);
6✔
72

73
        // Draw title
74
        painter->setPen(QColor(0, 0, 0));
6✔
75
        QFont title_font = painter->font();
6✔
76
        title_font.setBold(true);
6✔
77
        painter->setFont(title_font);
6✔
78

79
        QRectF title_rect = rect.adjusted(5, 5, -5, -rect.height() + 20);
6✔
80
        painter->drawText(title_rect, Qt::AlignCenter, getPlotTitle());
6✔
81
    }
6✔
82
}
6✔
83

UNCOV
84
void SpatialOverlayPlotWidget::mousePressEvent(QGraphicsSceneMouseEvent * event) {
×
85
    // Check if the click is in the title area (top 25 pixels)
86
    QRectF title_area = boundingRect().adjusted(0, 0, 0, -boundingRect().height() + 25);
×
87

UNCOV
88
    if (title_area.contains(event->pos())) {
×
89
        // Click in title area - handle selection and allow movement
90
        emit plotSelected(getPlotId());
×
91
        // Make sure the item is movable for dragging
92
        setFlag(QGraphicsItem::ItemIsMovable, true);
×
UNCOV
93
        AbstractPlotWidget::mousePressEvent(event);
×
94
    } else {
95
        // Click in content area - let the OpenGL widget handle it
96
        // But still emit selection signal
UNCOV
97
        emit plotSelected(getPlotId());
×
98
        // Disable movement when clicking in content area
99
        setFlag(QGraphicsItem::ItemIsMovable, false);
×
100
        // Don't call parent implementation to avoid interfering with OpenGL panning
UNCOV
101
        event->accept();
×
102
    }
103
}
×
104

105
void SpatialOverlayPlotWidget::keyPressEvent(QKeyEvent * event) {
×
106
    qDebug() << "SpatialOverlayPlotWidget::keyPressEvent - Key:" << event->key() << "Text:" << event->text();
×
107
    
108
    // Forward key events to the OpenGL widget using public handle method
UNCOV
109
    if (_opengl_widget) {
×
UNCOV
110
        qDebug() << "SpatialOverlayPlotWidget::keyPressEvent - Forwarding to OpenGL widget";
×
UNCOV
111
        _opengl_widget->handleKeyPress(event);
×
112
        qDebug() << "SpatialOverlayPlotWidget::keyPressEvent - Public handleKeyPress call completed";
×
113
        return; // Event was handled by OpenGL widget
×
114
    } else {
UNCOV
115
        qDebug() << "SpatialOverlayPlotWidget::keyPressEvent - No OpenGL widget available";
×
116
    }
117
    
118
    // If not handled by OpenGL widget, let parent handle it
UNCOV
119
    qDebug() << "SpatialOverlayPlotWidget::keyPressEvent - Calling parent implementation";
×
UNCOV
120
    AbstractPlotWidget::keyPressEvent(event);
×
121
}
122

123
void SpatialOverlayPlotWidget::resizeEvent(QGraphicsSceneResizeEvent * event) {
8✔
124
    AbstractPlotWidget::resizeEvent(event);
8✔
125

126
    if (_opengl_widget && _proxy_widget) {
8✔
127
        QRectF content_rect = isFrameAndTitleVisible()
8✔
128
            ? boundingRect().adjusted(2, 25, -2, -2)
9✔
129
            : boundingRect();
8✔
130
        _opengl_widget->resize(content_rect.size().toSize());
8✔
131
        _proxy_widget->setGeometry(content_rect);
8✔
132

133
        // Force update after resize
134
        _opengl_widget->update();
8✔
135
    }
136
}
8✔
137

138
void SpatialOverlayPlotWidget::updateVisualization() {
3✔
139

140
    if (_is_updating_visualization) {
3✔
UNCOV
141
        return;
×
142
    }
143

144
    if (!_parameters.data_manager || !_opengl_widget) {
3✔
UNCOV
145
        return;
×
146
    }
147

148
    _is_updating_visualization = true;
3✔
149

150
    loadPointData();
3✔
151
    loadMaskData();
3✔
152
    loadLineData();
3✔
153

154
    // Schedule a single coalesced render update for this tick
155
    scheduleRenderUpdate();
3✔
156
    _is_updating_visualization = false;
3✔
157
}
158

159
void SpatialOverlayPlotWidget::scheduleRenderUpdate() {
47✔
160
    if (_render_update_pending) {
47✔
161
        return;
25✔
162
    }
163
    _render_update_pending = true;
22✔
164
    // Use single-shot 0ms to coalesce multiple sources into one emission
165
    QTimer::singleShot(0, this, [this]() {
22✔
166
        _render_update_pending = false;
21✔
167
        update();
21✔
168
        emit renderUpdateRequested(getPlotId());
21✔
169
    });
21✔
170
}
171

172
void SpatialOverlayPlotWidget::handleFrameJumpRequest(EntityId entity_id, QString const & data_key) {
×
173

174
    //Get point data from entityID
175
    auto point_data = _parameters.data_manager->getData<PointData>(data_key.toStdString());
×
UNCOV
176
    if (point_data) {
×
UNCOV
177
        auto point = point_data->getTimeAndIndexByEntityId(entity_id);
×
UNCOV
178
        if (point) {
×
UNCOV
179
            emit frameJumpRequested(point->first.getValue(), data_key.toStdString());
×
180
        }
181
    }
UNCOV
182
}
×
183

184
void SpatialOverlayPlotWidget::loadPointData() {
3✔
185
    std::unordered_map<QString, std::shared_ptr<PointData>> point_data_map;
3✔
186

187
    for (QString const & key: _point_data_keys) {
6✔
188
        auto point_data = _parameters.data_manager->getData<PointData>(key.toStdString());
3✔
189
        if (point_data) {
3✔
190
            point_data_map[key] = point_data;
3✔
191
        }
192
    }
3✔
193
    // Always forward, even if empty, so the GL widget can clear its visualizations
194
    _opengl_widget->setPointData(point_data_map);
3✔
195
}
6✔
196

197
void SpatialOverlayPlotWidget::loadMaskData() {
3✔
198
    std::unordered_map<QString, std::shared_ptr<MaskData>> mask_data_map;
3✔
199

200
    for (QString const & key: _mask_data_keys) {
3✔
UNCOV
201
        auto mask_data = _parameters.data_manager->getData<MaskData>(key.toStdString());
×
UNCOV
202
        if (mask_data) {
×
UNCOV
203
            mask_data_map[key] = mask_data;
×
204
        }
UNCOV
205
    }
×
206
    // Always forward, even if empty, so the GL widget can clear its visualizations
207
    _opengl_widget->setMaskData(mask_data_map);
3✔
208
}
6✔
209

210
void SpatialOverlayPlotWidget::loadLineData() {
3✔
211
    std::unordered_map<QString, std::shared_ptr<LineData>> line_data_map;
3✔
212

213
    for (QString const & key: _line_data_keys) {
3✔
UNCOV
214
        auto line_data = _parameters.data_manager->getData<LineData>(key.toStdString());
×
UNCOV
215
        if (line_data) {
×
UNCOV
216
            line_data_map[key] = line_data;
×
217
        }
UNCOV
218
    }
×
219
    // Always forward, even if empty, so the GL widget can clear its visualizations
220
    _opengl_widget->setLineData(line_data_map);
3✔
221
}
6✔
222

223
void SpatialOverlayPlotWidget::setupOpenGLWidget() {
14✔
224
    _opengl_widget = new SpatialOverlayOpenGLWidget();
14✔
225
    
226
    _opengl_widget->setAttribute(Qt::WA_AlwaysStackOnTop, false);
14✔
227
    _opengl_widget->setAttribute(Qt::WA_OpaquePaintEvent, true);
14✔
228
    _opengl_widget->setAttribute(Qt::WA_NoSystemBackground, true);
14✔
229
    
230
    // Ensure the widget is properly initialized
231
    _opengl_widget->setUpdateBehavior(QOpenGLWidget::NoPartialUpdate);
14✔
232
    
233
    qDebug() << "SpatialOverlayPlotWidget: Created OpenGL widget with format:" << _opengl_widget->format().majorVersion() << "." << _opengl_widget->format().minorVersion();
14✔
234
    
235
    _proxy_widget = new QGraphicsProxyWidget(this);
14✔
236
    _proxy_widget->setWidget(_opengl_widget);
14✔
237

238
    // Configure the proxy widget to not interfere with parent interactions
239
    _proxy_widget->setFlag(QGraphicsItem::ItemIsMovable, false);
14✔
240
    _proxy_widget->setFlag(QGraphicsItem::ItemIsSelectable, false);
14✔
241
    
242
    // Set cache mode for better OpenGL rendering
243
    _proxy_widget->setCacheMode(QGraphicsItem::NoCache);
14✔
244

245
    // Set initial size and position
246
    QRectF content_rect = boundingRect().adjusted(2, 25, -2, -2);
14✔
247
    _opengl_widget->resize(content_rect.size().toSize());
14✔
248
    _proxy_widget->setGeometry(content_rect);
14✔
249

250
    // Connect signals from the refactored base class
251
    connect(_opengl_widget, &BasePlotOpenGLWidget::viewBoundsChanged,
42✔
252
            this, [this](BoundingBox const& bounds) {
28✔
253
                Q_UNUSED(bounds)
254
                // Coalesce render updates across sources
255
                scheduleRenderUpdate();
26✔
256
            });
26✔
257

258
    connect(_opengl_widget, &BasePlotOpenGLWidget::highlightStateChanged,
42✔
259
            this, [this]() {
28✔
260
                scheduleRenderUpdate();
14✔
261
            });
14✔
262

263
    connect(_opengl_widget, &BasePlotOpenGLWidget::mouseWorldMoved,
42✔
264
            this, [this](float world_x, float world_y) {
28✔
265
                Q_UNUSED(world_x) Q_UNUSED(world_y)
266
                // Could emit world coordinates if needed by parent widgets
267
            });
6✔
268

269
    // Connect property change signals to trigger updates
270
    connect(_opengl_widget, &SpatialOverlayOpenGLWidget::pointSizeChanged,
42✔
271
            this, [this](float) {
28✔
272
                scheduleRenderUpdate();
×
UNCOV
273
                emit renderingPropertiesChanged();
×
UNCOV
274
            });
×
275

276
    connect(_opengl_widget, &SpatialOverlayOpenGLWidget::tooltipsEnabledChanged,
42✔
277
            this, [this](bool) {
28✔
UNCOV
278
                emit renderingPropertiesChanged();
×
279
            });
×
280

281
    // Connect spatial overlay specific signals
282
    connect(_opengl_widget, &SpatialOverlayOpenGLWidget::frameJumpRequested,
42✔
283
            this, &SpatialOverlayPlotWidget::handleFrameJumpRequest);
28✔
284
}
14✔
285

UNCOV
286
void SpatialOverlayPlotWidget::setSelectionMode(SelectionMode mode) {
×
UNCOV
287
    if (_opengl_widget) {
×
UNCOV
288
        _opengl_widget->setSelectionMode(mode);
×
289
    }
UNCOV
290
}
×
291

292
SelectionMode SpatialOverlayPlotWidget::getSelectionMode() const {
19✔
293
    if (_opengl_widget) {
19✔
294
        return _opengl_widget->getSelectionMode();
19✔
295
    }
UNCOV
296
    return SelectionMode::None;
×
297
}
298

299
void SpatialOverlayPlotWidget::setGroupManager(GroupManager * group_manager) {
6✔
300
    // Call parent implementation
301
    AbstractPlotWidget::setGroupManager(group_manager);
6✔
302

303
    // Pass group manager to OpenGL widget
304
    if (_opengl_widget) {
6✔
305
        _opengl_widget->setGroupManager(group_manager);
6✔
306
    }
307
}
6✔
308

309
void SpatialOverlayPlotWidget::onGroupCreated(int group_id, QString const & name, QColor const & color) {
2✔
310
    Q_UNUSED(name)
311
    Q_UNUSED(color)
312
    if (_opengl_widget) {
2✔
313
        // Ask GL to refresh its per-point group render data based on current GroupManager state
314
        _opengl_widget->refreshGroupRenderDataAll();
2✔
315
    }
316
    // Coalesce scene-level updates as well
317
    scheduleRenderUpdate();
2✔
318
}
2✔
319

320
void SpatialOverlayPlotWidget::onGroupRemoved(int group_id) {
×
321
    Q_UNUSED(group_id)
UNCOV
322
    if (_opengl_widget) {
×
323
        // Ask GL to refresh its per-point group render data based on current GroupManager state
UNCOV
324
        _opengl_widget->refreshGroupRenderDataAll();
×
325
    }
326
    // Coalesce scene-level updates as well
UNCOV
327
    scheduleRenderUpdate();
×
UNCOV
328
}
×
329

330
void SpatialOverlayPlotWidget::onGroupPropertiesChanged(int group_id) {
2✔
331
    Q_UNUSED(group_id)
332
    if (_opengl_widget) {
2✔
333
        // Ask GL to refresh its per-point group render data based on current GroupManager state
334
        _opengl_widget->refreshGroupRenderDataAll();
2✔
335
    }
336
    // Coalesce scene-level updates as well
337
    scheduleRenderUpdate();
2✔
338
}
2✔
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