]> sigrok.org Git - pulseview.git/blame - pv/view/analogsignal.cpp
AnalogSignal: Allow separate vdiv counts for pos/neg
[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>
368a37c2 28#include <QComboBox>
4cffac16 29#include <QFormLayout>
368a37c2
SA
30#include <QGridLayout>
31#include <QLabel>
4cffac16 32#include <QSpinBox>
03cc651d 33#include <QString>
37b9fed4 34
2acdb232
JH
35#include "analogsignal.hpp"
36#include "pv/data/analog.hpp"
f3d66e52 37#include "pv/data/analogsegment.hpp"
bf0edd2b 38#include "pv/data/signalbase.hpp"
2acdb232 39#include "pv/view/view.hpp"
aba1dd16 40
fe3a1c21 41#include <libsigrokcxx/libsigrokcxx.hpp>
e8d00928 42
819f4c25 43using std::max;
a5d93c27 44using std::make_pair;
819f4c25 45using std::min;
f9abf97e 46using std::shared_ptr;
819f4c25 47using std::deque;
aba1dd16
JH
48
49namespace pv {
f4e57597
SA
50namespace views {
51namespace TraceView {
aba1dd16 52
568b90d4
JH
53const QColor AnalogSignal::SignalColours[4] = {
54 QColor(0xC4, 0xA0, 0x00), // Yellow
55 QColor(0x87, 0x20, 0x7A), // Magenta
56 QColor(0x20, 0x4A, 0x87), // Blue
57 QColor(0x4E, 0x9A, 0x06) // Green
58};
59
46a0cadc
SA
60const QColor AnalogSignal::GridMajorColor = QColor(0, 0, 0, 40*256/100);
61const QColor AnalogSignal::GridMinorColor = QColor(0, 0, 0, 20*256/100);
37b9fed4 62
7c68ddae
JH
63const float AnalogSignal::EnvelopeThreshold = 256.0f;
64
4cffac16 65const int AnalogSignal::MaximumVDivs = 10;
368a37c2
SA
66const int AnalogSignal::MinScaleIndex = -6;
67const int AnalogSignal::MaxScaleIndex = 7;
4cffac16 68
03cc651d
SA
69const int AnalogSignal::InfoTextMarginRight = 20;
70const int AnalogSignal::InfoTextMarginBottom = 5;
71
b86aa8f4 72AnalogSignal::AnalogSignal(
2b81ae46 73 pv::Session &session,
cbd2a2de 74 shared_ptr<data::SignalBase> base) :
73a25a6e 75 Signal(session, base),
834a4f1b 76 scale_index_(4), // 20 per div
37b9fed4
SA
77 scale_index_drag_offset_(0),
78 div_height_(3 * QFontMetrics(QApplication::font()).height()),
459db2c5
SA
79 pos_vdivs_(1),
80 neg_vdivs_(1),
834a4f1b 81 resolution_(0)
aba1dd16 82{
73a25a6e 83 base_->set_colour(SignalColours[base_->index() % countof(SignalColours)]);
834a4f1b 84 update_scale();
aba1dd16
JH
85}
86
3009b5b3
JH
87shared_ptr<pv::data::SignalData> AnalogSignal::data() const
88{
cbd2a2de 89 return base_->analog_data();
9a0cd293
JH
90}
91
3a21afa6
SA
92void AnalogSignal::save_settings(QSettings &settings) const
93{
459db2c5
SA
94 settings.setValue("pos_vdivs", pos_vdivs_);
95 settings.setValue("neg_vdivs", neg_vdivs_);
3a21afa6
SA
96 settings.setValue("scale_index", scale_index_);
97}
98
99void AnalogSignal::restore_settings(QSettings &settings)
100{
459db2c5
SA
101 if (settings.contains("pos_vdivs"))
102 pos_vdivs_ = settings.value("pos_vdivs").toInt();
103
104 if (settings.contains("neg_vdivs"))
105 neg_vdivs_ = settings.value("neg_vdivs").toInt();
3a21afa6
SA
106
107 if (settings.contains("scale_index")) {
108 scale_index_ = settings.value("scale_index").toInt();
109 update_scale();
110 }
111}
112
a5d93c27
JH
113std::pair<int, int> AnalogSignal::v_extents() const
114{
459db2c5
SA
115 const int ph = pos_vdivs_ * div_height_;
116 const int nh = neg_vdivs_ * div_height_;
117 return make_pair(-ph, nh);
a5d93c27
JH
118}
119
214470fc
JH
120int AnalogSignal::scale_handle_offset() const
121{
459db2c5 122 const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
37b9fed4 123
8a2fafcc 124 return ((scale_index_drag_offset_ - scale_index_) *
37b9fed4 125 h / 4) - h / 2;
214470fc
JH
126}
127
128void AnalogSignal::scale_handle_dragged(int offset)
129{
459db2c5 130 const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
37b9fed4 131
8a2fafcc 132 scale_index_ = scale_index_drag_offset_ -
37b9fed4 133 (offset + h / 2) / (h / 4);
834a4f1b
SA
134
135 update_scale();
8a2fafcc
JH
136}
137
138void AnalogSignal::scale_handle_drag_release()
139{
140 scale_index_drag_offset_ = scale_index_;
834a4f1b 141 update_scale();
214470fc
JH
142}
143
5b5fa4da 144void AnalogSignal::paint_back(QPainter &p, const ViewItemPaintParams &pp)
fe08b6e8 145{
73a25a6e 146 if (base_->enabled()) {
99af6802 147 Trace::paint_back(p, pp);
97904bf7 148 paint_axis(p, pp, get_visual_y());
99af6802 149 }
fe08b6e8
JH
150}
151
5b5fa4da 152void AnalogSignal::paint_mid(QPainter &p, const ViewItemPaintParams &pp)
aba1dd16 153{
cbd2a2de 154 assert(base_->analog_data());
8dbbc7f0 155 assert(owner_);
a8acb46e 156
be9e7b4b 157 const int y = get_visual_y();
01fd3263 158
73a25a6e 159 if (!base_->enabled())
cec48d16
JH
160 return;
161
37b9fed4
SA
162 paint_grid(p, y, pp.left(), pp.right());
163
f3d66e52 164 const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
cbd2a2de 165 base_->analog_data()->analog_segments();
f3d66e52 166 if (segments.empty())
a8acb46e
JH
167 return;
168
f3d66e52
JH
169 const shared_ptr<pv::data::AnalogSegment> &segment =
170 segments.front();
a8acb46e 171
4c8a6a6d 172 const double pixels_offset = pp.pixels_offset();
69e33a1b 173 const double samplerate = max(1.0, segment->samplerate());
60d9b99a 174 const pv::util::Timestamp& start_time = segment->start_time();
f3d66e52 175 const int64_t last_sample = segment->get_sample_count() - 1;
4c8a6a6d 176 const double samples_per_pixel = samplerate * pp.scale();
60d9b99a
JS
177 const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
178 const pv::util::Timestamp end = start + samples_per_pixel * pp.width();
a8acb46e 179
60d9b99a 180 const int64_t start_sample = min(max(floor(start).convert_to<int64_t>(),
a8acb46e 181 (int64_t)0), last_sample);
60d9b99a 182 const int64_t end_sample = min(max((ceil(end) + 1).convert_to<int64_t>(),
a8acb46e
JH
183 (int64_t)0), last_sample);
184
7c68ddae 185 if (samples_per_pixel < EnvelopeThreshold)
f3d66e52 186 paint_trace(p, segment, y, pp.left(),
7c68ddae
JH
187 start_sample, end_sample,
188 pixels_offset, samples_per_pixel);
189 else
f3d66e52 190 paint_envelope(p, segment, y, pp.left(),
7c68ddae
JH
191 start_sample, end_sample,
192 pixels_offset, samples_per_pixel);
193}
194
03cc651d
SA
195void AnalogSignal::paint_fore(QPainter &p, const ViewItemPaintParams &pp)
196{
197 if (!enabled())
198 return;
199
200 const int y = get_visual_y();
201
202 // Show the info section on the right side of the trace
203 const QString infotext = QString("%1 V/div").arg(resolution_);
204
73a25a6e 205 p.setPen(base_->colour());
03cc651d
SA
206 p.setFont(QApplication::font());
207
208 const QRectF bounding_rect = QRectF(pp.left(),
209 y + v_extents().first,
210 pp.width() - InfoTextMarginRight,
211 v_extents().second - v_extents().first - InfoTextMarginBottom);
212
213 p.drawText(bounding_rect, Qt::AlignRight | Qt::AlignBottom, infotext);
214}
215
37b9fed4
SA
216void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right)
217{
46a0cadc
SA
218 p.setRenderHint(QPainter::Antialiasing, false);
219
459db2c5
SA
220 if (pos_vdivs_ > 0) {
221 p.setPen(QPen(GridMajorColor, 1, Qt::DashLine));
222 for (int i = 1; i <= pos_vdivs_; i++) {
223 const float dy = i * div_height_;
224 p.drawLine(QLineF(left, y - dy, right, y - dy));
225 }
226
227 p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
228 for (int i = 0; i < pos_vdivs_; i++) {
229 const float dy = i * div_height_;
230 const float dy25 = dy + (0.25 * div_height_);
231 const float dy50 = dy + (0.50 * div_height_);
232 const float dy75 = dy + (0.75 * div_height_);
233 p.drawLine(QLineF(left, y - dy25, right, y - dy25));
234 p.drawLine(QLineF(left, y - dy50, right, y - dy50));
235 p.drawLine(QLineF(left, y - dy75, right, y - dy75));
236 }
37b9fed4
SA
237 }
238
459db2c5
SA
239 if (neg_vdivs_ > 0) {
240 p.setPen(QPen(GridMajorColor, 1, Qt::DashLine));
241 for (int i = 1; i <= neg_vdivs_; i++) {
242 const float dy = i * div_height_;
243 p.drawLine(QLineF(left, y + dy, right, y + dy));
244 }
245
246 p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
247 for (int i = 0; i < neg_vdivs_; i++) {
248 const float dy = i * div_height_;
249 const float dy25 = dy + (0.25 * div_height_);
250 const float dy50 = dy + (0.50 * div_height_);
251 const float dy75 = dy + (0.75 * div_height_);
252 p.drawLine(QLineF(left, y + dy25, right, y + dy25));
253 p.drawLine(QLineF(left, y + dy50, right, y + dy50));
254 p.drawLine(QLineF(left, y + dy75, right, y + dy75));
255 }
37b9fed4 256 }
46a0cadc
SA
257
258 p.setRenderHint(QPainter::Antialiasing, true);
37b9fed4
SA
259}
260
7c68ddae 261void AnalogSignal::paint_trace(QPainter &p,
f3d66e52 262 const shared_ptr<pv::data::AnalogSegment> &segment,
7c68ddae
JH
263 int y, int left, const int64_t start, const int64_t end,
264 const double pixels_offset, const double samples_per_pixel)
265{
73a25a6e 266 p.setPen(base_->colour());
7c68ddae 267
26a883ed 268 QPointF *points = new QPointF[end - start];
a8acb46e
JH
269 QPointF *point = points;
270
26a883ed
SA
271 pv::data::SegmentAnalogDataIterator* it =
272 segment->begin_sample_iteration(start);
273
7c68ddae 274 for (int64_t sample = start; sample != end; sample++) {
a8acb46e 275 const float x = (sample / samples_per_pixel -
2658961b 276 pixels_offset) + left;
26a883ed
SA
277
278 *point++ = QPointF(x, y - *((float*)it->value) * scale_);
279 segment->continue_sample_iteration(it, 1);
a8acb46e 280 }
26a883ed 281 segment->end_sample_iteration(it);
a8acb46e 282
306d43a7 283 p.drawPolyline(points, point - points);
a8acb46e
JH
284
285 delete[] points;
aba1dd16
JH
286}
287
7c68ddae 288void AnalogSignal::paint_envelope(QPainter &p,
f3d66e52 289 const shared_ptr<pv::data::AnalogSegment> &segment,
7c68ddae
JH
290 int y, int left, const int64_t start, const int64_t end,
291 const double pixels_offset, const double samples_per_pixel)
292{
f3d66e52 293 using pv::data::AnalogSegment;
7c68ddae 294
f3d66e52
JH
295 AnalogSegment::EnvelopeSection e;
296 segment->get_envelope_section(e, start, end, samples_per_pixel);
7c68ddae
JH
297
298 if (e.length < 2)
299 return;
300
819f4c25 301 p.setPen(QPen(Qt::NoPen));
73a25a6e 302 p.setBrush(base_->colour());
7c68ddae
JH
303
304 QRectF *const rects = new QRectF[e.length];
305 QRectF *rect = rects;
306
f3290553 307 for (uint64_t sample = 0; sample < e.length-1; sample++) {
7c68ddae
JH
308 const float x = ((e.scale * sample + e.start) /
309 samples_per_pixel - pixels_offset) + left;
f3d66e52 310 const AnalogSegment::EnvelopeSample *const s =
7c68ddae
JH
311 e.samples + sample;
312
313 // We overlap this sample with the next so that vertical
314 // gaps do not appear during steep rising or falling edges
834a4f1b
SA
315 const float b = y - max(s->max, (s+1)->min) * scale_;
316 const float t = y - min(s->min, (s+1)->max) * scale_;
7c68ddae
JH
317
318 float h = b - t;
f3290553 319 if (h >= 0.0f && h <= 1.0f)
7c68ddae 320 h = 1.0f;
f3290553 321 if (h <= 0.0f && h >= -1.0f)
7c68ddae
JH
322 h = -1.0f;
323
324 *rect++ = QRectF(x, t, 1.0f, h);
325 }
326
327 p.drawRects(rects, e.length);
328
329 delete[] rects;
330 delete[] e.samples;
331}
332
368a37c2 333float AnalogSignal::get_resolution(int scale_index)
8a2fafcc
JH
334{
335 const float seq[] = {1.0f, 2.0f, 5.0f};
834a4f1b 336
8a2fafcc
JH
337 const int offset = std::numeric_limits<int>::max() / (2 * countof(seq));
338 const std::div_t d = std::div(
368a37c2 339 (int)(scale_index + countof(seq) * offset),
8a2fafcc 340 countof(seq));
834a4f1b 341
368a37c2
SA
342 return powf(10.0f, d.quot - offset) * seq[d.rem];
343}
344
345void AnalogSignal::update_scale()
346{
347 resolution_ = get_resolution(scale_index_);
834a4f1b 348 scale_ = div_height_ / resolution_;
8a2fafcc
JH
349}
350
4cffac16
SA
351void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
352{
353 // Add the standard options
354 Signal::populate_popup_form(parent, form);
355
368a37c2
SA
356 QFormLayout *const layout = new QFormLayout;
357
358 // Add the number of vdivs
459db2c5
SA
359 QSpinBox *pvdiv_sb = new QSpinBox(parent);
360 pvdiv_sb->setRange(0, MaximumVDivs);
361 pvdiv_sb->setValue(pos_vdivs_);
362 connect(pvdiv_sb, SIGNAL(valueChanged(int)),
363 this, SLOT(on_pos_vdivs_changed(int)));
364 layout->addRow(tr("Number of pos vertical divs"), pvdiv_sb);
365
366 QSpinBox *nvdiv_sb = new QSpinBox(parent);
367 nvdiv_sb->setRange(0, MaximumVDivs);
368 nvdiv_sb->setValue(neg_vdivs_);
369 connect(nvdiv_sb, SIGNAL(valueChanged(int)),
370 this, SLOT(on_neg_vdivs_changed(int)));
371 layout->addRow(tr("Number of neg vertical divs"), nvdiv_sb);
368a37c2
SA
372
373 // Add the vertical resolution
374 resolution_cb_ = new QComboBox(parent);
375
376 for (int i = MinScaleIndex; i < MaxScaleIndex; i++) {
377 const QString label = QString("%1").arg(get_resolution(i));
378 resolution_cb_->insertItem(0, label, QVariant(i));
379 }
380
381 const int cur_idx = resolution_cb_->findData(QVariant(scale_index_));
382 resolution_cb_->setCurrentIndex(cur_idx);
383
384 connect(resolution_cb_, SIGNAL(currentIndexChanged(int)),
385 this, SLOT(on_resolution_changed(int)));
386
387 QGridLayout *const vdiv_layout = new QGridLayout;
388 QLabel *const vdiv_unit = new QLabel(tr("V/div"));
389 vdiv_layout->addWidget(resolution_cb_, 0, 0);
390 vdiv_layout->addWidget(vdiv_unit, 0, 1);
391
392 layout->addRow(tr("Vertical resolution"), vdiv_layout);
393
394 form->addRow(layout);
4cffac16
SA
395}
396
459db2c5
SA
397void AnalogSignal::on_pos_vdivs_changed(int vdivs)
398{
399 pos_vdivs_ = vdivs;
400
401 if (owner_) {
402 // Call order is important, otherwise the lazy event handler won't work
403 owner_->extents_changed(false, true);
404 owner_->row_item_appearance_changed(false, true);
405 }
406}
407
408void AnalogSignal::on_neg_vdivs_changed(int vdivs)
4cffac16 409{
459db2c5 410 neg_vdivs_ = vdivs;
4cffac16 411
75d0779e
SA
412 if (owner_) {
413 // Call order is important, otherwise the lazy event handler won't work
4cffac16 414 owner_->extents_changed(false, true);
75d0779e
SA
415 owner_->row_item_appearance_changed(false, true);
416 }
4cffac16
SA
417}
418
368a37c2
SA
419void AnalogSignal::on_resolution_changed(int index)
420{
421 scale_index_ = resolution_cb_->itemData(index).toInt();
422 update_scale();
423
424 if (owner_)
425 owner_->row_item_appearance_changed(false, true);
426}
4cffac16 427
f4e57597
SA
428} // namespace TraceView
429} // namespace views
aba1dd16 430} // namespace pv