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