]> sigrok.org Git - pulseview.git/blame - pv/view/analogsignal.cpp
Only show sampling points when zoomed in far enough.
[pulseview.git] / pv / view / analogsignal.cpp
CommitLineData
aba1dd16
JH
1/*
2 * This file is part of the PulseView project.
3 *
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
efdec55a 17 * along with this program; if not, see <http://www.gnu.org/licenses/>.
aba1dd16
JH
18 */
19
20#include <extdef.h>
21
3b68d03d
JH
22#include <cassert>
23#include <cmath>
8a2fafcc
JH
24#include <cstdlib>
25#include <limits>
aba1dd16 26
37b9fed4 27#include <QApplication>
73e377fe 28#include <QCheckBox>
368a37c2 29#include <QComboBox>
4cffac16 30#include <QFormLayout>
368a37c2
SA
31#include <QGridLayout>
32#include <QLabel>
4cffac16 33#include <QSpinBox>
03cc651d 34#include <QString>
37b9fed4 35
2acdb232
JH
36#include "analogsignal.hpp"
37#include "pv/data/analog.hpp"
f3d66e52 38#include "pv/data/analogsegment.hpp"
bf0edd2b 39#include "pv/data/signalbase.hpp"
2acdb232 40#include "pv/view/view.hpp"
8de1e1b2 41#include "pv/globalsettings.hpp"
aba1dd16 42
fe3a1c21 43#include <libsigrokcxx/libsigrokcxx.hpp>
e8d00928 44
819f4c25 45using std::max;
a5d93c27 46using std::make_pair;
819f4c25 47using std::min;
f9abf97e 48using std::shared_ptr;
819f4c25 49using std::deque;
aba1dd16
JH
50
51namespace pv {
f4e57597
SA
52namespace views {
53namespace TraceView {
aba1dd16 54
568b90d4
JH
55const QColor AnalogSignal::SignalColours[4] = {
56 QColor(0xC4, 0xA0, 0x00), // Yellow
57 QColor(0x87, 0x20, 0x7A), // Magenta
58 QColor(0x20, 0x4A, 0x87), // Blue
59 QColor(0x4E, 0x9A, 0x06) // Green
60};
61
46a0cadc
SA
62const QColor AnalogSignal::GridMajorColor = QColor(0, 0, 0, 40*256/100);
63const QColor AnalogSignal::GridMinorColor = QColor(0, 0, 0, 20*256/100);
37b9fed4 64
8de1e1b2
UH
65const QColor AnalogSignal::SamplingPointColour(0x77, 0x77, 0x77);
66
7c68ddae
JH
67const float AnalogSignal::EnvelopeThreshold = 256.0f;
68
4cffac16 69const int AnalogSignal::MaximumVDivs = 10;
368a37c2
SA
70const int AnalogSignal::MinScaleIndex = -6;
71const int AnalogSignal::MaxScaleIndex = 7;
4cffac16 72
03cc651d
SA
73const int AnalogSignal::InfoTextMarginRight = 20;
74const int AnalogSignal::InfoTextMarginBottom = 5;
75
b86aa8f4 76AnalogSignal::AnalogSignal(
2b81ae46 77 pv::Session &session,
cbd2a2de 78 shared_ptr<data::SignalBase> base) :
73a25a6e 79 Signal(session, base),
834a4f1b 80 scale_index_(4), // 20 per div
37b9fed4
SA
81 scale_index_drag_offset_(0),
82 div_height_(3 * QFontMetrics(QApplication::font()).height()),
459db2c5
SA
83 pos_vdivs_(1),
84 neg_vdivs_(1),
73e377fe 85 resolution_(0),
1f1d55ce 86 autoranging_(true)
aba1dd16 87{
85715407
SA
88 pv::data::Analog* analog_data =
89 dynamic_cast<pv::data::Analog*>(data().get());
90
91 connect(analog_data, SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
92 this, SLOT(on_samples_added()));
93
73a25a6e 94 base_->set_colour(SignalColours[base_->index() % countof(SignalColours)]);
834a4f1b 95 update_scale();
aba1dd16
JH
96}
97
3009b5b3
JH
98shared_ptr<pv::data::SignalData> AnalogSignal::data() const
99{
cbd2a2de 100 return base_->analog_data();
9a0cd293
JH
101}
102
3a21afa6
SA
103void AnalogSignal::save_settings(QSettings &settings) const
104{
459db2c5
SA
105 settings.setValue("pos_vdivs", pos_vdivs_);
106 settings.setValue("neg_vdivs", neg_vdivs_);
3a21afa6 107 settings.setValue("scale_index", scale_index_);
73e377fe 108 settings.setValue("autoranging", autoranging_);
3a21afa6
SA
109}
110
111void AnalogSignal::restore_settings(QSettings &settings)
112{
459db2c5
SA
113 if (settings.contains("pos_vdivs"))
114 pos_vdivs_ = settings.value("pos_vdivs").toInt();
115
116 if (settings.contains("neg_vdivs"))
117 neg_vdivs_ = settings.value("neg_vdivs").toInt();
3a21afa6
SA
118
119 if (settings.contains("scale_index")) {
120 scale_index_ = settings.value("scale_index").toInt();
121 update_scale();
122 }
73e377fe
SA
123
124 if (settings.contains("autoranging"))
125 autoranging_ = settings.value("autoranging").toBool();
3a21afa6
SA
126}
127
a5d93c27
JH
128std::pair<int, int> AnalogSignal::v_extents() const
129{
459db2c5
SA
130 const int ph = pos_vdivs_ * div_height_;
131 const int nh = neg_vdivs_ * div_height_;
132 return make_pair(-ph, nh);
a5d93c27
JH
133}
134
214470fc
JH
135int AnalogSignal::scale_handle_offset() const
136{
459db2c5 137 const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
37b9fed4 138
8a2fafcc 139 return ((scale_index_drag_offset_ - scale_index_) *
37b9fed4 140 h / 4) - h / 2;
214470fc
JH
141}
142
143void AnalogSignal::scale_handle_dragged(int offset)
144{
459db2c5 145 const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
37b9fed4 146
8a2fafcc 147 scale_index_ = scale_index_drag_offset_ -
37b9fed4 148 (offset + h / 2) / (h / 4);
834a4f1b
SA
149
150 update_scale();
8a2fafcc
JH
151}
152
153void AnalogSignal::scale_handle_drag_release()
154{
155 scale_index_drag_offset_ = scale_index_;
834a4f1b 156 update_scale();
214470fc
JH
157}
158
5b5fa4da 159void AnalogSignal::paint_back(QPainter &p, const ViewItemPaintParams &pp)
fe08b6e8 160{
73a25a6e 161 if (base_->enabled()) {
99af6802 162 Trace::paint_back(p, pp);
97904bf7 163 paint_axis(p, pp, get_visual_y());
99af6802 164 }
fe08b6e8
JH
165}
166
5b5fa4da 167void AnalogSignal::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
aba1dd16 168{
cbd2a2de 169 assert(base_->analog_data());
8dbbc7f0 170 assert(owner_);
a8acb46e 171
be9e7b4b 172 const int y = get_visual_y();
01fd3263 173
73a25a6e 174 if (!base_->enabled())
cec48d16
JH
175 return;
176
37b9fed4
SA
177 paint_grid(p, y, pp.left(), pp.right());
178
f3d66e52 179 const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
cbd2a2de 180 base_->analog_data()->analog_segments();
f3d66e52 181 if (segments.empty())
a8acb46e
JH
182 return;
183
f3d66e52
JH
184 const shared_ptr<pv::data::AnalogSegment> &segment =
185 segments.front();
a8acb46e 186
4c8a6a6d 187 const double pixels_offset = pp.pixels_offset();
69e33a1b 188 const double samplerate = max(1.0, segment->samplerate());
60d9b99a 189 const pv::util::Timestamp& start_time = segment->start_time();
f3d66e52 190 const int64_t last_sample = segment->get_sample_count() - 1;
4c8a6a6d 191 const double samples_per_pixel = samplerate * pp.scale();
60d9b99a
JS
192 const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
193 const pv::util::Timestamp end = start + samples_per_pixel * pp.width();
a8acb46e 194
60d9b99a 195 const int64_t start_sample = min(max(floor(start).convert_to<int64_t>(),
a8acb46e 196 (int64_t)0), last_sample);
60d9b99a 197 const int64_t end_sample = min(max((ceil(end) + 1).convert_to<int64_t>(),
a8acb46e
JH
198 (int64_t)0), last_sample);
199
7c68ddae 200 if (samples_per_pixel < EnvelopeThreshold)
f3d66e52 201 paint_trace(p, segment, y, pp.left(),
7c68ddae
JH
202 start_sample, end_sample,
203 pixels_offset, samples_per_pixel);
204 else
f3d66e52 205 paint_envelope(p, segment, y, pp.left(),
7c68ddae
JH
206 start_sample, end_sample,
207 pixels_offset, samples_per_pixel);
208}
209
03cc651d
SA
210void AnalogSignal::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
211{
212 if (!enabled())
213 return;
214
215 const int y = get_visual_y();
216
217 // Show the info section on the right side of the trace
218 const QString infotext = QString("%1 V/div").arg(resolution_);
219
73a25a6e 220 p.setPen(base_->colour());
03cc651d
SA
221 p.setFont(QApplication::font());
222
223 const QRectF bounding_rect = QRectF(pp.left(),
224 y + v_extents().first,
225 pp.width() - InfoTextMarginRight,
226 v_extents().second - v_extents().first - InfoTextMarginBottom);
227
228 p.drawText(bounding_rect, Qt::AlignRight | Qt::AlignBottom, infotext);
229}
230
37b9fed4
SA
231void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right)
232{
46a0cadc
SA
233 p.setRenderHint(QPainter::Antialiasing, false);
234
459db2c5
SA
235 if (pos_vdivs_ > 0) {
236 p.setPen(QPen(GridMajorColor, 1, Qt::DashLine));
237 for (int i = 1; i <= pos_vdivs_; i++) {
238 const float dy = i * div_height_;
239 p.drawLine(QLineF(left, y - dy, right, y - dy));
240 }
241
242 p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
243 for (int i = 0; i < pos_vdivs_; i++) {
244 const float dy = i * div_height_;
245 const float dy25 = dy + (0.25 * div_height_);
246 const float dy50 = dy + (0.50 * div_height_);
247 const float dy75 = dy + (0.75 * div_height_);
248 p.drawLine(QLineF(left, y - dy25, right, y - dy25));
249 p.drawLine(QLineF(left, y - dy50, right, y - dy50));
250 p.drawLine(QLineF(left, y - dy75, right, y - dy75));
251 }
37b9fed4
SA
252 }
253
459db2c5
SA
254 if (neg_vdivs_ > 0) {
255 p.setPen(QPen(GridMajorColor, 1, Qt::DashLine));
256 for (int i = 1; i <= neg_vdivs_; i++) {
257 const float dy = i * div_height_;
258 p.drawLine(QLineF(left, y + dy, right, y + dy));
259 }
260
261 p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
262 for (int i = 0; i < neg_vdivs_; i++) {
263 const float dy = i * div_height_;
264 const float dy25 = dy + (0.25 * div_height_);
265 const float dy50 = dy + (0.50 * div_height_);
266 const float dy75 = dy + (0.75 * div_height_);
267 p.drawLine(QLineF(left, y + dy25, right, y + dy25));
268 p.drawLine(QLineF(left, y + dy50, right, y + dy50));
269 p.drawLine(QLineF(left, y + dy75, right, y + dy75));
270 }
37b9fed4 271 }
46a0cadc
SA
272
273 p.setRenderHint(QPainter::Antialiasing, true);
37b9fed4
SA
274}
275
7c68ddae 276void AnalogSignal::paint_trace(QPainter &p,
f3d66e52 277 const shared_ptr<pv::data::AnalogSegment> &segment,
7c68ddae
JH
278 int y, int left, const int64_t start, const int64_t end,
279 const double pixels_offset, const double samples_per_pixel)
280{
73a25a6e 281 p.setPen(base_->colour());
7c68ddae 282
76ce6c7a
UH
283 const int64_t points_count = end - start;
284
285 QPointF *points = new QPointF[points_count];
a8acb46e
JH
286 QPointF *point = points;
287
8de1e1b2
UH
288 QRectF *const sampling_points = new QRectF[points_count];
289 QRectF *sampling_point = sampling_points;
290
26a883ed
SA
291 pv::data::SegmentAnalogDataIterator* it =
292 segment->begin_sample_iteration(start);
293
8de1e1b2 294 const int w = 2;
7c68ddae 295 for (int64_t sample = start; sample != end; sample++) {
a8acb46e 296 const float x = (sample / samples_per_pixel -
2658961b 297 pixels_offset) + left;
26a883ed
SA
298
299 *point++ = QPointF(x, y - *((float*)it->value) * scale_);
8de1e1b2
UH
300 *sampling_point++ = QRectF(x - (w / 2), y - *((float*)it->value) * scale_ - (w / 2), w, w);
301
26a883ed 302 segment->continue_sample_iteration(it, 1);
a8acb46e 303 }
26a883ed 304 segment->end_sample_iteration(it);
a8acb46e 305
76ce6c7a 306 p.drawPolyline(points, points_count);
a8acb46e 307
8de1e1b2
UH
308 // Paint the sampling points if enabled
309 GlobalSettings settings;
310 const bool show_sampling_points =
311 settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool();
00f6bae9
UH
312
313 if (show_sampling_points && (samples_per_pixel < 0.25)) {
8de1e1b2
UH
314 p.setPen(SamplingPointColour);
315 p.drawRects(sampling_points, points_count);
316 }
317
a8acb46e 318 delete[] points;
8de1e1b2 319 delete[] sampling_points;
aba1dd16
JH
320}
321
7c68ddae 322void AnalogSignal::paint_envelope(QPainter &p,
f3d66e52 323 const shared_ptr<pv::data::AnalogSegment> &segment,
7c68ddae
JH
324 int y, int left, const int64_t start, const int64_t end,
325 const double pixels_offset, const double samples_per_pixel)
326{
f3d66e52 327 using pv::data::AnalogSegment;
7c68ddae 328
f3d66e52
JH
329 AnalogSegment::EnvelopeSection e;
330 segment->get_envelope_section(e, start, end, samples_per_pixel);
7c68ddae
JH
331
332 if (e.length < 2)
333 return;
334
819f4c25 335 p.setPen(QPen(Qt::NoPen));
73a25a6e 336 p.setBrush(base_->colour());
7c68ddae
JH
337
338 QRectF *const rects = new QRectF[e.length];
339 QRectF *rect = rects;
340
f3290553 341 for (uint64_t sample = 0; sample < e.length-1; sample++) {
7c68ddae
JH
342 const float x = ((e.scale * sample + e.start) /
343 samples_per_pixel - pixels_offset) + left;
f3d66e52 344 const AnalogSegment::EnvelopeSample *const s =
7c68ddae
JH
345 e.samples + sample;
346
347 // We overlap this sample with the next so that vertical
348 // gaps do not appear during steep rising or falling edges
834a4f1b
SA
349 const float b = y - max(s->max, (s+1)->min) * scale_;
350 const float t = y - min(s->min, (s+1)->max) * scale_;
7c68ddae
JH
351
352 float h = b - t;
f3290553 353 if (h >= 0.0f && h <= 1.0f)
7c68ddae 354 h = 1.0f;
f3290553 355 if (h <= 0.0f && h >= -1.0f)
7c68ddae
JH
356 h = -1.0f;
357
358 *rect++ = QRectF(x, t, 1.0f, h);
359 }
360
361 p.drawRects(rects, e.length);
362
363 delete[] rects;
364 delete[] e.samples;
365}
366
368a37c2 367float AnalogSignal::get_resolution(int scale_index)
8a2fafcc
JH
368{
369 const float seq[] = {1.0f, 2.0f, 5.0f};
834a4f1b 370
8a2fafcc
JH
371 const int offset = std::numeric_limits<int>::max() / (2 * countof(seq));
372 const std::div_t d = std::div(
368a37c2 373 (int)(scale_index + countof(seq) * offset),
8a2fafcc 374 countof(seq));
834a4f1b 375
368a37c2
SA
376 return powf(10.0f, d.quot - offset) * seq[d.rem];
377}
378
379void AnalogSignal::update_scale()
380{
381 resolution_ = get_resolution(scale_index_);
834a4f1b 382 scale_ = div_height_ / resolution_;
8a2fafcc
JH
383}
384
73e377fe
SA
385void AnalogSignal::perform_autoranging(bool force_update)
386{
387 const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
388 base_->analog_data()->analog_segments();
389
390 if (segments.empty())
391 return;
392
393 static double prev_min = 0, prev_max = 0;
394 double min = 0, max = 0;
395
396 for (shared_ptr<pv::data::AnalogSegment> segment : segments) {
397 std::pair<double, double> mm = segment->get_min_max();
398 min = std::min(min, mm.first);
399 max = std::max(max, mm.second);
400 }
401
402 if ((min == prev_min) && (max == prev_max) && !force_update)
403 return;
404
405 prev_min = min;
406 prev_max = max;
407
408 // Use all divs for the positive range if there are no negative values
409 if ((min == 0) && (neg_vdivs_ > 0)) {
410 pos_vdivs_ += neg_vdivs_;
411 neg_vdivs_ = 0;
412 }
413
681b6d5a
SA
414 // Split up the divs if there are negative values but no negative divs
415 if ((min < 0) && (neg_vdivs_ == 0)) {
416 neg_vdivs_ = pos_vdivs_ / 2;
417 pos_vdivs_ -= neg_vdivs_;
418 }
419
73e377fe
SA
420 double min_value_per_div;
421 if ((pos_vdivs_ > 0) && (neg_vdivs_ > 0))
422 min_value_per_div = std::max(max / pos_vdivs_, -min / neg_vdivs_);
423 else if (pos_vdivs_ > 0)
424 min_value_per_div = max / pos_vdivs_;
425 else
426 min_value_per_div = -min / neg_vdivs_;
427
428 // Find first scale value that is bigger than the value we need
429 for (int i = MinScaleIndex; i < MaxScaleIndex; i++)
430 if (get_resolution(i) > min_value_per_div) {
431 scale_index_ = i;
432 break;
433 }
434
435 update_scale();
436}
437
4cffac16
SA
438void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
439{
440 // Add the standard options
441 Signal::populate_popup_form(parent, form);
442
368a37c2
SA
443 QFormLayout *const layout = new QFormLayout;
444
445 // Add the number of vdivs
459db2c5
SA
446 QSpinBox *pvdiv_sb = new QSpinBox(parent);
447 pvdiv_sb->setRange(0, MaximumVDivs);
448 pvdiv_sb->setValue(pos_vdivs_);
449 connect(pvdiv_sb, SIGNAL(valueChanged(int)),
450 this, SLOT(on_pos_vdivs_changed(int)));
451 layout->addRow(tr("Number of pos vertical divs"), pvdiv_sb);
452
453 QSpinBox *nvdiv_sb = new QSpinBox(parent);
454 nvdiv_sb->setRange(0, MaximumVDivs);
455 nvdiv_sb->setValue(neg_vdivs_);
456 connect(nvdiv_sb, SIGNAL(valueChanged(int)),
457 this, SLOT(on_neg_vdivs_changed(int)));
458 layout->addRow(tr("Number of neg vertical divs"), nvdiv_sb);
368a37c2
SA
459
460 // Add the vertical resolution
461 resolution_cb_ = new QComboBox(parent);
462
463 for (int i = MinScaleIndex; i < MaxScaleIndex; i++) {
464 const QString label = QString("%1").arg(get_resolution(i));
465 resolution_cb_->insertItem(0, label, QVariant(i));
466 }
467
468 const int cur_idx = resolution_cb_->findData(QVariant(scale_index_));
469 resolution_cb_->setCurrentIndex(cur_idx);
470
471 connect(resolution_cb_, SIGNAL(currentIndexChanged(int)),
472 this, SLOT(on_resolution_changed(int)));
473
474 QGridLayout *const vdiv_layout = new QGridLayout;
475 QLabel *const vdiv_unit = new QLabel(tr("V/div"));
476 vdiv_layout->addWidget(resolution_cb_, 0, 0);
477 vdiv_layout->addWidget(vdiv_unit, 0, 1);
478
479 layout->addRow(tr("Vertical resolution"), vdiv_layout);
480
73e377fe
SA
481 // Add the autoranging checkbox
482 QCheckBox* autoranging_cb = new QCheckBox();
483 autoranging_cb->setCheckState(autoranging_ ? Qt::Checked : Qt::Unchecked);
484
485 connect(autoranging_cb, SIGNAL(stateChanged(int)),
486 this, SLOT(on_autoranging_changed(int)));
487
488 layout->addRow(tr("Autoranging"), autoranging_cb);
489
368a37c2 490 form->addRow(layout);
4cffac16
SA
491}
492
85715407
SA
493void AnalogSignal::on_samples_added()
494{
495 perform_autoranging();
496
497 if (owner_) {
498 // Call order is important, otherwise the lazy event handler won't work
499 owner_->extents_changed(false, true);
500 owner_->row_item_appearance_changed(false, true);
501 }
502}
503
459db2c5
SA
504void AnalogSignal::on_pos_vdivs_changed(int vdivs)
505{
506 pos_vdivs_ = vdivs;
507
73e377fe
SA
508 if (autoranging_)
509 perform_autoranging(true);
510
459db2c5
SA
511 if (owner_) {
512 // Call order is important, otherwise the lazy event handler won't work
513 owner_->extents_changed(false, true);
514 owner_->row_item_appearance_changed(false, true);
515 }
516}
517
518void AnalogSignal::on_neg_vdivs_changed(int vdivs)
4cffac16 519{
459db2c5 520 neg_vdivs_ = vdivs;
4cffac16 521
73e377fe
SA
522 if (autoranging_)
523 perform_autoranging(true);
524
75d0779e
SA
525 if (owner_) {
526 // Call order is important, otherwise the lazy event handler won't work
4cffac16 527 owner_->extents_changed(false, true);
75d0779e
SA
528 owner_->row_item_appearance_changed(false, true);
529 }
4cffac16
SA
530}
531
368a37c2
SA
532void AnalogSignal::on_resolution_changed(int index)
533{
534 scale_index_ = resolution_cb_->itemData(index).toInt();
535 update_scale();
536
537 if (owner_)
538 owner_->row_item_appearance_changed(false, true);
539}
4cffac16 540
73e377fe
SA
541void AnalogSignal::on_autoranging_changed(int state)
542{
543 autoranging_ = (state == Qt::Checked);
544
545 if (autoranging_)
546 perform_autoranging(true);
547
85715407
SA
548 if (owner_) {
549 // Call order is important, otherwise the lazy event handler won't work
550 owner_->extents_changed(false, true);
73e377fe 551 owner_->row_item_appearance_changed(false, true);
85715407 552 }
73e377fe
SA
553}
554
f4e57597
SA
555} // namespace TraceView
556} // namespace views
aba1dd16 557} // namespace pv