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

paulmthompson / WhiskerToolbox / 18018707227

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

push

github

paulmthompson
selection for lines appears to work well

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

797 existing lines in 9 files now uncovered.

42024 of 61280 relevant lines covered (68.58%)

1133.34 hits per line

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

70.81
/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)
13✔
24
    : AbstractPlotWidget(parent),
25
      _opengl_widget(nullptr),
13✔
26
      _proxy_widget(nullptr) {
13✔
27

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

34
QString SpatialOverlayPlotWidget::getPlotType() const {
1✔
35
    return "Spatial Overlay Plot";
1✔
36
}
37

38
void SpatialOverlayPlotWidget::setDataKeys(QStringList const & point_data_keys,
3✔
39
                                           QStringList const & mask_data_keys,
40
                                           QStringList const & line_data_keys) {
41
    _point_data_keys = point_data_keys;
3✔
42
    _mask_data_keys = mask_data_keys;
3✔
43
    _line_data_keys = line_data_keys;
3✔
44
    updateVisualization();
3✔
45
}
3✔
46

47
void SpatialOverlayPlotWidget::paint(QPainter * painter, QStyleOptionGraphicsItem const * option, QWidget * widget) {
3✔
48
    Q_UNUSED(option)
49
    Q_UNUSED(widget)
50

51
    if (isFrameAndTitleVisible()) {
3✔
52
        // Draw frame around the plot
53
        QRectF rect = boundingRect();
3✔
54

55
        QPen border_pen;
3✔
56
        if (isSelected()) {
3✔
57
            border_pen.setColor(QColor(0, 120, 200));
×
58
            border_pen.setWidth(2);
×
59
        } else {
60
            border_pen.setColor(QColor(100, 100, 100));
3✔
61
            border_pen.setWidth(1);
3✔
62
        }
63
        painter->setPen(border_pen);
3✔
64
        painter->drawRect(rect);
3✔
65

66
        // Draw title
67
        painter->setPen(QColor(0, 0, 0));
3✔
68
        QFont title_font = painter->font();
3✔
69
        title_font.setBold(true);
3✔
70
        painter->setFont(title_font);
3✔
71

72
        QRectF title_rect = rect.adjusted(5, 5, -5, -rect.height() + 20);
3✔
73
        painter->drawText(title_rect, Qt::AlignCenter, getPlotTitle());
3✔
74
    }
3✔
75
}
3✔
76

77
void SpatialOverlayPlotWidget::mousePressEvent(QGraphicsSceneMouseEvent * event) {
×
78
    // Check if the click is in the title area (top 25 pixels)
79
    QRectF title_area = boundingRect().adjusted(0, 0, 0, -boundingRect().height() + 25);
×
80

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

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

116
void SpatialOverlayPlotWidget::resizeEvent(QGraphicsSceneResizeEvent * event) {
7✔
117
    AbstractPlotWidget::resizeEvent(event);
7✔
118

119
    if (_opengl_widget && _proxy_widget) {
7✔
120
        QRectF content_rect = isFrameAndTitleVisible()
7✔
121
            ? boundingRect().adjusted(2, 25, -2, -2)
7✔
122
            : boundingRect();
7✔
123
        _opengl_widget->resize(content_rect.size().toSize());
7✔
124
        _proxy_widget->setGeometry(content_rect);
7✔
125

126
        // Force update after resize
127
        _opengl_widget->update();
7✔
128
    }
129
}
7✔
130

131
void SpatialOverlayPlotWidget::updateVisualization() {
3✔
132

133
    if (_is_updating_visualization) {
3✔
134
        return;
×
135
    }
136

137
    if (!_parameters.data_manager || !_opengl_widget) {
3✔
138
        return;
×
139
    }
140

141
    _is_updating_visualization = true;
3✔
142

143
    loadPointData();
3✔
144
    loadMaskData();
3✔
145
    loadLineData();
3✔
146

147
    // Schedule a single coalesced render update for this tick
148
    scheduleRenderUpdate();
3✔
149
    _is_updating_visualization = false;
3✔
150
}
151

152
void SpatialOverlayPlotWidget::scheduleRenderUpdate() {
47✔
153
    if (_render_update_pending) {
47✔
154
        return;
14✔
155
    }
156
    _render_update_pending = true;
33✔
157
    // Use single-shot 0ms to coalesce multiple sources into one emission
158
    QTimer::singleShot(0, this, [this]() {
33✔
159
        _render_update_pending = false;
32✔
160
        update();
32✔
161
        emit renderUpdateRequested(getPlotId());
32✔
162
    });
32✔
163
}
164

165
void SpatialOverlayPlotWidget::handleFrameJumpRequest(EntityId entity_id, QString const & data_key) {
×
166

167
    //Get point data from entityID
168
    auto point_data = _parameters.data_manager->getData<PointData>(data_key.toStdString());
×
169
    if (point_data) {
×
170
        auto point = point_data->getTimeAndIndexByEntityId(entity_id);
×
171
        if (point) {
×
172
            emit frameJumpRequested(point->first.getValue(), data_key.toStdString());
×
173
        }
174
    }
175
}
×
176

177
void SpatialOverlayPlotWidget::loadPointData() {
3✔
178
    std::unordered_map<QString, std::shared_ptr<PointData>> point_data_map;
3✔
179

180
    for (QString const & key: _point_data_keys) {
6✔
181
        auto point_data = _parameters.data_manager->getData<PointData>(key.toStdString());
3✔
182
        if (point_data) {
3✔
183
            point_data_map[key] = point_data;
3✔
184
        }
185
    }
3✔
186
    // Always forward, even if empty, so the GL widget can clear its visualizations
187
    _opengl_widget->setPointData(point_data_map);
3✔
188
}
6✔
189

190
void SpatialOverlayPlotWidget::loadMaskData() {
3✔
191
    std::unordered_map<QString, std::shared_ptr<MaskData>> mask_data_map;
3✔
192

193
    for (QString const & key: _mask_data_keys) {
3✔
194
        auto mask_data = _parameters.data_manager->getData<MaskData>(key.toStdString());
×
195
        if (mask_data) {
×
196
            mask_data_map[key] = mask_data;
×
197
        }
198
    }
×
199
    // Always forward, even if empty, so the GL widget can clear its visualizations
200
    _opengl_widget->setMaskData(mask_data_map);
3✔
201
}
6✔
202

203
void SpatialOverlayPlotWidget::loadLineData() {
3✔
204
    std::unordered_map<QString, std::shared_ptr<LineData>> line_data_map;
3✔
205

206
    for (QString const & key: _line_data_keys) {
3✔
207
        auto line_data = _parameters.data_manager->getData<LineData>(key.toStdString());
×
208
        if (line_data) {
×
209
            line_data_map[key] = line_data;
×
210
        }
211
    }
×
212
    // Always forward, even if empty, so the GL widget can clear its visualizations
213
    _opengl_widget->setLineData(line_data_map);
3✔
214
}
6✔
215

216
void SpatialOverlayPlotWidget::setupOpenGLWidget() {
13✔
217
    _opengl_widget = new SpatialOverlayOpenGLWidget();
13✔
218
    
219
    _opengl_widget->setAttribute(Qt::WA_AlwaysStackOnTop, false);
13✔
220
    _opengl_widget->setAttribute(Qt::WA_OpaquePaintEvent, true);
13✔
221
    _opengl_widget->setAttribute(Qt::WA_NoSystemBackground, true);
13✔
222
    
223
    // Ensure the widget is properly initialized
224
    _opengl_widget->setUpdateBehavior(QOpenGLWidget::NoPartialUpdate);
13✔
225
    
226
    qDebug() << "SpatialOverlayPlotWidget: Created OpenGL widget with format:" << _opengl_widget->format().majorVersion() << "." << _opengl_widget->format().minorVersion();
13✔
227
    
228
    _proxy_widget = new QGraphicsProxyWidget(this);
13✔
229
    _proxy_widget->setWidget(_opengl_widget);
13✔
230

231
    // Configure the proxy widget to not interfere with parent interactions
232
    _proxy_widget->setFlag(QGraphicsItem::ItemIsMovable, false);
13✔
233
    _proxy_widget->setFlag(QGraphicsItem::ItemIsSelectable, false);
13✔
234
    
235
    // Set cache mode for better OpenGL rendering
236
    _proxy_widget->setCacheMode(QGraphicsItem::NoCache);
13✔
237

238
    // Set initial size and position
239
    QRectF content_rect = boundingRect().adjusted(2, 25, -2, -2);
13✔
240
    _opengl_widget->resize(content_rect.size().toSize());
13✔
241
    _proxy_widget->setGeometry(content_rect);
13✔
242

243
    // Connect signals from the refactored base class
244
    connect(_opengl_widget, &BasePlotOpenGLWidget::viewBoundsChanged,
39✔
245
            this, [this](BoundingBox const& bounds) {
26✔
246
                Q_UNUSED(bounds)
247
                // Coalesce render updates across sources
248
                scheduleRenderUpdate();
20✔
249
            });
20✔
250

251
    connect(_opengl_widget, &BasePlotOpenGLWidget::highlightStateChanged,
39✔
252
            this, [this]() {
26✔
253
                scheduleRenderUpdate();
20✔
254
            });
20✔
255

256
    connect(_opengl_widget, &BasePlotOpenGLWidget::mouseWorldMoved,
39✔
257
            this, [this](float world_x, float world_y) {
26✔
258
                Q_UNUSED(world_x) Q_UNUSED(world_y)
259
                // Could emit world coordinates if needed by parent widgets
260
            });
4✔
261

262
    // Connect property change signals to trigger updates
263
    connect(_opengl_widget, &SpatialOverlayOpenGLWidget::pointSizeChanged,
39✔
264
            this, [this](float) {
26✔
265
                scheduleRenderUpdate();
×
266
                emit renderingPropertiesChanged();
×
267
            });
×
268

269
    connect(_opengl_widget, &SpatialOverlayOpenGLWidget::tooltipsEnabledChanged,
39✔
270
            this, [this](bool) {
26✔
271
                emit renderingPropertiesChanged();
×
272
            });
×
273

274
    // Connect spatial overlay specific signals
275
    connect(_opengl_widget, &SpatialOverlayOpenGLWidget::frameJumpRequested,
39✔
276
            this, &SpatialOverlayPlotWidget::handleFrameJumpRequest);
26✔
277
}
13✔
278

279
void SpatialOverlayPlotWidget::setSelectionMode(SelectionMode mode) {
×
280
    if (_opengl_widget) {
×
281
        _opengl_widget->setSelectionMode(mode);
×
282
    }
283
}
×
284

285
SelectionMode SpatialOverlayPlotWidget::getSelectionMode() const {
19✔
286
    if (_opengl_widget) {
19✔
287
        return _opengl_widget->getSelectionMode();
19✔
288
    }
289
    return SelectionMode::None;
×
290
}
291

292
void SpatialOverlayPlotWidget::setGroupManager(GroupManager * group_manager) {
6✔
293
    // Call parent implementation
294
    AbstractPlotWidget::setGroupManager(group_manager);
6✔
295

296
    // Pass group manager to OpenGL widget
297
    if (_opengl_widget) {
6✔
298
        _opengl_widget->setGroupManager(group_manager);
6✔
299
    }
300
}
6✔
301

302
void SpatialOverlayPlotWidget::onGroupCreated(int group_id, QString const & name, QColor const & color) {
2✔
303
    Q_UNUSED(name)
304
    Q_UNUSED(color)
305
    if (_opengl_widget) {
2✔
306
        // Ask GL to refresh its per-point group render data based on current GroupManager state
307
        _opengl_widget->refreshGroupRenderDataAll();
2✔
308
    }
309
    // Coalesce scene-level updates as well
310
    scheduleRenderUpdate();
2✔
311
}
2✔
312

UNCOV
313
void SpatialOverlayPlotWidget::onGroupRemoved(int group_id) {
×
314
    Q_UNUSED(group_id)
UNCOV
315
    if (_opengl_widget) {
×
316
        // Ask GL to refresh its per-point group render data based on current GroupManager state
UNCOV
317
        _opengl_widget->refreshGroupRenderDataAll();
×
318
    }
319
    // Coalesce scene-level updates as well
UNCOV
320
    scheduleRenderUpdate();
×
UNCOV
321
}
×
322

323
void SpatialOverlayPlotWidget::onGroupPropertiesChanged(int group_id) {
2✔
324
    Q_UNUSED(group_id)
325
    if (_opengl_widget) {
2✔
326
        // Ask GL to refresh its per-point group render data based on current GroupManager state
327
        _opengl_widget->refreshGroupRenderDataAll();
2✔
328
    }
329
    // Coalesce scene-level updates as well
330
    scheduleRenderUpdate();
2✔
331
}
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