v2.0.0
Loading...
Searching...
No Matches
overviewbarwidget.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "overviewbarwidget.h"
18#include "channeldatamodel.h"
19
20//=============================================================================================================
21// QT INCLUDES
22//=============================================================================================================
23
24#include <QPainter>
25#include <QMouseEvent>
26#include <QtMath>
27
28//=============================================================================================================
29// USED NAMESPACES
30//=============================================================================================================
31
32using namespace DISPLIB;
33
34//=============================================================================================================
35// DEFINE MEMBER METHODS
36//=============================================================================================================
37
39: QWidget(parent)
40{
41 setFixedHeight(kBarHeight);
42 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
43 setCursor(Qt::PointingHandCursor);
44}
45
46//=============================================================================================================
47
49{
50 return QSize(400, kBarHeight);
51}
52
53//=============================================================================================================
54
56{
57 return QSize(100, kBarHeight);
58}
59
60//=============================================================================================================
61
63{
64 m_model = model;
65 m_envelopeDirty = true;
66 update();
67}
68
69//=============================================================================================================
70
72{
73 m_firstFileSample = first;
74 m_envelopeDirty = true;
75 update();
76}
77
78//=============================================================================================================
79
81{
82 m_lastFileSample = last;
83 m_envelopeDirty = true;
84 update();
85}
86
87//=============================================================================================================
88
90{
91 m_sfreq = sfreq;
92 update();
93}
94
95//=============================================================================================================
96
97void OverviewBarWidget::setViewport(float scrollSample, float visibleSamples)
98{
99 m_scrollSample = scrollSample;
100 m_visibleSamples = visibleSamples;
101 update();
102}
103
104//=============================================================================================================
105
106void OverviewBarWidget::setEvents(const QVector<ChannelRhiView::EventMarker>& events)
107{
108 m_events = events;
109 update();
110}
111
112//=============================================================================================================
113
114void OverviewBarWidget::setAnnotations(const QVector<ChannelRhiView::AnnotationSpan>& annotations)
115{
116 m_annotations = annotations;
117 update();
118}
119
120//=============================================================================================================
121
122float OverviewBarWidget::xToSample(int x) const
123{
124 int totalSamples = m_lastFileSample - m_firstFileSample;
125 if (totalSamples <= 0 || width() <= 0)
126 return static_cast<float>(m_firstFileSample);
127 float frac = static_cast<float>(x) / static_cast<float>(width());
128 frac = qBound(0.f, frac, 1.f);
129 return static_cast<float>(m_firstFileSample) + frac * static_cast<float>(totalSamples);
130}
131
132//=============================================================================================================
133
134void OverviewBarWidget::rebuildEnvelope()
135{
136 const int pw = width();
137 const int ph = kBarHeight;
138 if (pw <= 0 || !m_model || m_lastFileSample <= m_firstFileSample) {
139 m_envelopeImage = QImage();
140 m_envelopeDirty = false;
141 return;
142 }
143
144 m_envelopeImage = QImage(pw, ph, QImage::Format_RGB32);
145 m_envelopeImage.fill(QColor(38, 38, 42).rgb()); // dark background
146
147 QPainter p(&m_envelopeImage);
148 p.setRenderHint(QPainter::Antialiasing, false);
149
150 const int totalSamples = m_lastFileSample - m_firstFileSample;
151 const float samplesPerPx = static_cast<float>(totalSamples) / static_cast<float>(pw);
152 const int nChannels = m_model->channelCount();
153 if (nChannels <= 0 || totalSamples <= 0) {
154 m_envelopeDirty = false;
155 return;
156 }
157
158 // Group channels by type and compute a per-pixel RMS envelope for each type
159 struct TypeEnvelope
160 {
161 QString typeLabel;
162 QColor color;
163 QVector<float> envelope; // per-pixel RMS, size = pw
164 };
165 QVector<TypeEnvelope> typeEnvelopes;
166 QMap<QString, int> typeIndex;
167
168 for (int ch = 0; ch < nChannels; ++ch) {
169 auto info = m_model->channelInfo(ch);
170 if (!typeIndex.contains(info.typeLabel)) {
171 int idx = typeEnvelopes.size();
172 typeIndex[info.typeLabel] = idx;
173 TypeEnvelope te;
174 te.typeLabel = info.typeLabel;
175 te.color = info.color;
176 te.envelope.resize(pw, 0.f);
177 typeEnvelopes.append(te);
178 }
179 }
180
181 // For each pixel column, compute max absolute value across channels of each type
182 for (int px_col = 0; px_col < pw; ++px_col) {
183 int sampleStart = m_firstFileSample + static_cast<int>(px_col * samplesPerPx);
184 int sampleEnd = m_firstFileSample + static_cast<int>((px_col + 1) * samplesPerPx);
185 sampleEnd = qMin(sampleEnd, m_lastFileSample);
186 if (sampleEnd <= sampleStart)
187 continue;
188
189 // Sample a few points in this range for speed
190 int step = qMax(1, (sampleEnd - sampleStart) / 4);
191 for (int ch = 0; ch < nChannels; ++ch) {
192 auto info = m_model->channelInfo(ch);
193 int ti = typeIndex.value(info.typeLabel, -1);
194 if (ti < 0)
195 continue;
196
197 float maxAbs = 0.f;
198 for (int s = sampleStart; s < sampleEnd; s += step) {
199 float v = qAbs(m_model->sampleValueAt(ch, s));
200 if (v > maxAbs)
201 maxAbs = v;
202 }
203 // Normalise by amplitude scale
204 float norm = (info.amplitudeMax > 0.f) ? maxAbs / info.amplitudeMax : 0.f;
205 if (norm > typeEnvelopes[ti].envelope[px_col])
206 typeEnvelopes[ti].envelope[px_col] = norm;
207 }
208 }
209
210 // Draw each type's envelope as a filled area from the bottom
211 const int nTypes = typeEnvelopes.size();
212 const float laneH = static_cast<float>(ph) / qMax(nTypes, 1);
213
214 for (int ti = 0; ti < nTypes; ++ti) {
215 const auto& te = typeEnvelopes[ti];
216 QColor fillColor = te.color;
217 fillColor.setAlpha(160);
218 p.setPen(Qt::NoPen);
219
220 float yBase = (ti + 1) * laneH;
221 for (int x = 0; x < pw; ++x) {
222 float level = qBound(0.f, te.envelope[x], 1.f);
223 float barH = level * laneH * 0.9f;
224 if (barH < 0.5f)
225 continue;
226 p.fillRect(QRectF(x, yBase - barH, 1.f, barH), fillColor);
227 }
228
229 // Type label
230 QFont f = font();
231 f.setPointSizeF(7.0);
232 p.setFont(f);
233 p.setPen(QColor(200, 200, 200, 180));
234 p.drawText(QRectF(2, static_cast<qreal>(ti) * laneH, 60, laneH * 0.5f),
235 Qt::AlignLeft | Qt::AlignTop, te.typeLabel);
236 }
237
238 m_envelopeDirty = false;
239}
240
241//=============================================================================================================
242
244{
245 QPainter p(this);
246 p.setRenderHint(QPainter::Antialiasing, false);
247
248 const int pw = width();
249 const int ph = height();
250
251 if (m_envelopeDirty || m_envelopeImage.width() != pw)
252 rebuildEnvelope();
253
254 // Draw the envelope image or a plain dark background
255 if (!m_envelopeImage.isNull()) {
256 p.drawImage(0, 0, m_envelopeImage);
257 } else {
258 p.fillRect(rect(), QColor(38, 38, 42));
259 }
260
261 int totalSamples = m_lastFileSample - m_firstFileSample;
262 if (totalSamples <= 0) {
263 // No data loaded yet — show placeholder text
264 p.setPen(QColor(120, 120, 130));
265 p.drawText(rect(), Qt::AlignCenter, QStringLiteral("No data"));
266 return;
267 }
268
269 float samplesPerPx = static_cast<float>(totalSamples) / static_cast<float>(pw);
270
271 // ── Annotation spans ────────────────────────────────────────────
272 for (const auto& ann : m_annotations) {
273 float xStart = static_cast<float>(ann.startSample - m_firstFileSample) / samplesPerPx;
274 float xEnd = static_cast<float>(ann.endSample - m_firstFileSample) / samplesPerPx;
275 xStart = qBound(0.f, xStart, static_cast<float>(pw));
276 xEnd = qBound(0.f, xEnd, static_cast<float>(pw));
277 if (xEnd > xStart) {
278 QColor c = ann.color;
279 c.setAlpha(60);
280 p.fillRect(QRectF(xStart, 0, xEnd - xStart, ph), c);
281 }
282 }
283
284 // ── Event markers ───────────────────────────────────────────────
285 for (const auto& ev : m_events) {
286 float xF = static_cast<float>(ev.sample - m_firstFileSample) / samplesPerPx;
287 if (xF < 0.f || xF > pw)
288 continue;
289 QColor c = ev.color;
290 c.setAlpha(200);
291 p.setPen(QPen(c, 1));
292 int ix = static_cast<int>(xF);
293 p.drawLine(ix, ph - 6, ix, ph);
294 }
295
296 // ── Viewport rectangle ──────────────────────────────────────────
297 float vpX = (m_scrollSample - static_cast<float>(m_firstFileSample)) / samplesPerPx;
298 float vpW = m_visibleSamples / samplesPerPx;
299 vpX = qBound(0.f, vpX, static_cast<float>(pw));
300 vpW = qBound(2.f, vpW, static_cast<float>(pw) - vpX);
301
302 // Semi-transparent overlay outside the viewport
303 QColor dimColor(0, 0, 0, 100);
304 if (vpX > 0.f)
305 p.fillRect(QRectF(0, 0, vpX, ph), dimColor);
306 if (vpX + vpW < pw)
307 p.fillRect(QRectF(vpX + vpW, 0, pw - vpX - vpW, ph), dimColor);
308
309 // Viewport border
310 QPen vpPen(QColor(255, 255, 255, 200), 1.5);
311 p.setPen(vpPen);
312 p.setBrush(Qt::NoBrush);
313 p.drawRect(QRectF(vpX, 0.5f, vpW, ph - 1.f));
314}
315
316//=============================================================================================================
317
319{
320 if (event->button() == Qt::LeftButton) {
321 m_dragging = true;
322 float targetSample = xToSample(event->position().toPoint().x()) - m_visibleSamples * 0.5f;
323 emit scrollRequested(targetSample);
324 event->accept();
325 }
326}
327
328//=============================================================================================================
329
330void OverviewBarWidget::mouseMoveEvent(QMouseEvent* event)
331{
332 if (m_dragging) {
333 float targetSample = xToSample(event->position().toPoint().x()) - m_visibleSamples * 0.5f;
334 emit scrollRequested(targetSample);
335 event->accept();
336 }
337}
338
339//=============================================================================================================
340
342{
343 if (event->button() == Qt::LeftButton) {
344 m_dragging = false;
345 event->accept();
346 }
347}
Minimap thumbnail of the entire recording shown above the raw browser.
Circular-buffer Qt model exposing a rolling window of the live FIFF stream as a table.
2-D display widgets and visualisation helpers (charts, topography, colour maps).
Circular-buffer Qt model exposing a rolling window of the live FIFF stream as a table.
void scrollRequested(float targetSample)
void paintEvent(QPaintEvent *event) override
QSize sizeHint() const override
QSize minimumSizeHint() const override
void setAnnotations(const QVector< ChannelRhiView::AnnotationSpan > &annotations)
void setModel(ChannelDataModel *model)
void mouseMoveEvent(QMouseEvent *event) override
void setEvents(const QVector< ChannelRhiView::EventMarker > &events)
void mousePressEvent(QMouseEvent *event) override
void mouseReleaseEvent(QMouseEvent *event) override
OverviewBarWidget(QWidget *parent=nullptr)
void setViewport(float scrollSample, float visibleSamples)
static constexpr int kBarHeight