]> sigrok.org Git - pulseview.git/blob - pv/views/trace/analogsignal.cpp
f3046c0aaee2db8215b9399dda100a0c4700bf7b
[pulseview.git] / pv / views / trace / analogsignal.cpp
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
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <extdef.h>
21
22 #include <cassert>
23 #include <cmath>
24 #include <cstdlib>
25 #include <limits>
26 #include <vector>
27
28 #include <QApplication>
29 #include <QCheckBox>
30 #include <QComboBox>
31 #include <QFormLayout>
32 #include <QGridLayout>
33 #include <QLabel>
34 #include <QString>
35
36 #include "analogsignal.hpp"
37 #include "logicsignal.hpp"
38 #include "view.hpp"
39
40 #include "pv/data/analog.hpp"
41 #include "pv/data/analogsegment.hpp"
42 #include "pv/data/logic.hpp"
43 #include "pv/data/logicsegment.hpp"
44 #include "pv/data/signalbase.hpp"
45 #include "pv/globalsettings.hpp"
46
47 #include <libsigrokcxx/libsigrokcxx.hpp>
48
49 using std::deque;
50 using std::div;
51 using std::div_t;
52 using std::max;
53 using std::make_pair;
54 using std::min;
55 using std::numeric_limits;
56 using std::pair;
57 using std::shared_ptr;
58 using std::vector;
59
60 using pv::data::SignalBase;
61
62 namespace pv {
63 namespace views {
64 namespace trace {
65
66 const QColor AnalogSignal::SignalColours[4] = {
67         QColor(0xC4, 0xA0, 0x00),       // Yellow
68         QColor(0x87, 0x20, 0x7A),       // Magenta
69         QColor(0x20, 0x4A, 0x87),       // Blue
70         QColor(0x4E, 0x9A, 0x06)        // Green
71 };
72
73 const QColor AnalogSignal::GridMajorColor = QColor(0, 0, 0, 40 * 256 / 100);
74 const QColor AnalogSignal::GridMinorColor = QColor(0, 0, 0, 20 * 256 / 100);
75
76 const QColor AnalogSignal::SamplingPointColour(0x77, 0x77, 0x77);
77
78 const int64_t AnalogSignal::TracePaintBlockSize = 1024 * 1024;  // 4 MiB (due to float)
79 const float AnalogSignal::EnvelopeThreshold = 64.0f;
80
81 const int AnalogSignal::MaximumVDivs = 10;
82 const int AnalogSignal::MinScaleIndex = -6;
83 const int AnalogSignal::MaxScaleIndex = 7;
84
85 const int AnalogSignal::InfoTextMarginRight = 20;
86 const int AnalogSignal::InfoTextMarginBottom = 5;
87
88 AnalogSignal::AnalogSignal(
89         pv::Session &session,
90         shared_ptr<data::SignalBase> base) :
91         Signal(session, base),
92         scale_index_(4), // 20 per div
93         scale_index_drag_offset_(0),
94         pos_vdivs_(1),
95         neg_vdivs_(1),
96         resolution_(0),
97         display_type_(DisplayBoth),
98         autoranging_(true)
99 {
100         pv::data::Analog* analog_data =
101                 dynamic_cast<pv::data::Analog*>(data().get());
102
103         connect(analog_data, SIGNAL(samples_added(QObject*, uint64_t, uint64_t)),
104                 this, SLOT(on_samples_added()));
105
106         connect(&delayed_conversion_starter_, SIGNAL(timeout()),
107                 this, SLOT(on_delayed_conversion_starter()));
108         delayed_conversion_starter_.setSingleShot(true);
109         delayed_conversion_starter_.setInterval(1000);  // 1s timeout
110
111         GlobalSettings gs;
112         div_height_ = gs.value(GlobalSettings::Key_View_DefaultDivHeight).toInt();
113
114         base_->set_colour(SignalColours[base_->index() % countof(SignalColours)]);
115         update_scale();
116 }
117
118 shared_ptr<pv::data::SignalData> AnalogSignal::data() const
119 {
120         return base_->analog_data();
121 }
122
123 void AnalogSignal::save_settings(QSettings &settings) const
124 {
125         settings.setValue("pos_vdivs", pos_vdivs_);
126         settings.setValue("neg_vdivs", neg_vdivs_);
127         settings.setValue("scale_index", scale_index_);
128         settings.setValue("display_type", display_type_);
129         settings.setValue("autoranging", autoranging_);
130         settings.setValue("div_height", div_height_);
131 }
132
133 void AnalogSignal::restore_settings(QSettings &settings)
134 {
135         if (settings.contains("pos_vdivs"))
136                 pos_vdivs_ = settings.value("pos_vdivs").toInt();
137
138         if (settings.contains("neg_vdivs"))
139                 neg_vdivs_ = settings.value("neg_vdivs").toInt();
140
141         if (settings.contains("scale_index")) {
142                 scale_index_ = settings.value("scale_index").toInt();
143                 update_scale();
144         }
145
146         if (settings.contains("display_type"))
147                 display_type_ = (DisplayType)(settings.value("display_type").toInt());
148
149         if (settings.contains("autoranging"))
150                 autoranging_ = settings.value("autoranging").toBool();
151
152         if (settings.contains("div_height")) {
153                 const int old_height = div_height_;
154                 div_height_ = settings.value("div_height").toInt();
155
156                 if ((div_height_ != old_height) && owner_) {
157                         // Call order is important, otherwise the lazy event handler won't work
158                         owner_->extents_changed(false, true);
159                         owner_->row_item_appearance_changed(false, true);
160                 }
161         }
162 }
163
164 pair<int, int> AnalogSignal::v_extents() const
165 {
166         const int ph = pos_vdivs_ * div_height_;
167         const int nh = neg_vdivs_ * div_height_;
168         return make_pair(-ph, nh);
169 }
170
171 int AnalogSignal::scale_handle_offset() const
172 {
173         const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
174
175         return ((scale_index_drag_offset_ - scale_index_) * h / 4) - h / 2;
176 }
177
178 void AnalogSignal::scale_handle_dragged(int offset)
179 {
180         const int h = (pos_vdivs_ + neg_vdivs_) * div_height_;
181
182         scale_index_ = scale_index_drag_offset_ - (offset + h / 2) / (h / 4);
183
184         update_scale();
185 }
186
187 void AnalogSignal::scale_handle_drag_release()
188 {
189         scale_index_drag_offset_ = scale_index_;
190         update_scale();
191 }
192
193 void AnalogSignal::paint_back(QPainter &p, ViewItemPaintParams &pp)
194 {
195         if (base_->enabled()) {
196                 Trace::paint_back(p, pp);
197                 paint_axis(p, pp, get_visual_y());
198         }
199 }
200
201 void AnalogSignal::paint_mid(QPainter &p, ViewItemPaintParams &pp)
202 {
203         assert(base_->analog_data());
204         assert(owner_);
205
206         const int y = get_visual_y();
207
208         if (!base_->enabled())
209                 return;
210
211         if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) {
212                 paint_grid(p, y, pp.left(), pp.right());
213
214                 const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
215                         base_->analog_data()->analog_segments();
216                 if (segments.empty())
217                         return;
218
219                 const shared_ptr<pv::data::AnalogSegment> &segment =
220                         segments.front();
221
222                 const double pixels_offset = pp.pixels_offset();
223                 const double samplerate = max(1.0, segment->samplerate());
224                 const pv::util::Timestamp& start_time = segment->start_time();
225                 const int64_t last_sample = segment->get_sample_count() - 1;
226                 const double samples_per_pixel = samplerate * pp.scale();
227                 const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
228                 const pv::util::Timestamp end = start + samples_per_pixel * pp.width();
229
230                 const int64_t start_sample = min(max(floor(start).convert_to<int64_t>(),
231                         (int64_t)0), last_sample);
232                 const int64_t end_sample = min(max((ceil(end) + 1).convert_to<int64_t>(),
233                         (int64_t)0), last_sample);
234
235                 if (samples_per_pixel < EnvelopeThreshold)
236                         paint_trace(p, segment, y, pp.left(),
237                                 start_sample, end_sample,
238                                 pixels_offset, samples_per_pixel);
239                 else
240                         paint_envelope(p, segment, y, pp.left(),
241                                 start_sample, end_sample,
242                                 pixels_offset, samples_per_pixel);
243         }
244
245         if ((display_type_ == DisplayConverted) || (display_type_ == DisplayBoth)) {
246                 const SignalBase::ConversionType conv_type = base_->get_conversion_type();
247
248                 if (((conv_type == SignalBase::A2LConversionByTreshold) ||
249                         (conv_type == SignalBase::A2LConversionBySchmittTrigger)))
250                         paint_logic_mid(p, pp);
251         }
252 }
253
254 void AnalogSignal::paint_fore(QPainter &p, ViewItemPaintParams &pp)
255 {
256         if (!enabled())
257                 return;
258
259         if ((display_type_ == DisplayAnalog) || (display_type_ == DisplayBoth)) {
260                 const int y = get_visual_y();
261
262                 // Show the info section on the right side of the trace
263                 const QString infotext = QString("%1 V/div").arg(resolution_);
264
265                 p.setPen(base_->colour());
266                 p.setFont(QApplication::font());
267
268                 const QRectF bounding_rect = QRectF(pp.left(),
269                                 y + v_extents().first,
270                                 pp.width() - InfoTextMarginRight,
271                                 v_extents().second - v_extents().first - InfoTextMarginBottom);
272
273                 p.drawText(bounding_rect, Qt::AlignRight | Qt::AlignBottom, infotext);
274         }
275 }
276
277 void AnalogSignal::paint_grid(QPainter &p, int y, int left, int right)
278 {
279         p.setRenderHint(QPainter::Antialiasing, false);
280
281         GlobalSettings settings;
282         const bool show_analog_minor_grid =
283                 settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool();
284
285         if (pos_vdivs_ > 0) {
286                 p.setPen(QPen(GridMajorColor, 1, Qt::DashLine));
287                 for (int i = 1; i <= pos_vdivs_; i++) {
288                         const float dy = i * div_height_;
289                         p.drawLine(QLineF(left, y - dy, right, y - dy));
290                 }
291         }
292
293         if ((pos_vdivs_ > 0) && show_analog_minor_grid) {
294                 p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
295                 for (int i = 0; i < pos_vdivs_; i++) {
296                         const float dy = i * div_height_;
297                         const float dy25 = dy + (0.25 * div_height_);
298                         const float dy50 = dy + (0.50 * div_height_);
299                         const float dy75 = dy + (0.75 * div_height_);
300                         p.drawLine(QLineF(left, y - dy25, right, y - dy25));
301                         p.drawLine(QLineF(left, y - dy50, right, y - dy50));
302                         p.drawLine(QLineF(left, y - dy75, right, y - dy75));
303                 }
304         }
305
306         if (neg_vdivs_ > 0) {
307                 p.setPen(QPen(GridMajorColor, 1, Qt::DashLine));
308                 for (int i = 1; i <= neg_vdivs_; i++) {
309                         const float dy = i * div_height_;
310                         p.drawLine(QLineF(left, y + dy, right, y + dy));
311                 }
312         }
313
314         if ((pos_vdivs_ > 0) && show_analog_minor_grid) {
315                 p.setPen(QPen(GridMinorColor, 1, Qt::DashLine));
316                 for (int i = 0; i < neg_vdivs_; i++) {
317                         const float dy = i * div_height_;
318                         const float dy25 = dy + (0.25 * div_height_);
319                         const float dy50 = dy + (0.50 * div_height_);
320                         const float dy75 = dy + (0.75 * div_height_);
321                         p.drawLine(QLineF(left, y + dy25, right, y + dy25));
322                         p.drawLine(QLineF(left, y + dy50, right, y + dy50));
323                         p.drawLine(QLineF(left, y + dy75, right, y + dy75));
324                 }
325         }
326
327         p.setRenderHint(QPainter::Antialiasing, true);
328 }
329
330 void AnalogSignal::paint_trace(QPainter &p,
331         const shared_ptr<pv::data::AnalogSegment> &segment,
332         int y, int left, const int64_t start, const int64_t end,
333         const double pixels_offset, const double samples_per_pixel)
334 {
335         if (end <= start)
336                 return;
337
338         // Calculate and paint the sampling points if enabled and useful
339         GlobalSettings settings;
340         const bool show_sampling_points =
341                 settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() &&
342                 (samples_per_pixel < 0.25);
343
344         p.setPen(base_->colour());
345
346         const int64_t points_count = end - start;
347
348         QPointF *points = new QPointF[points_count];
349         QPointF *point = points;
350
351         QRectF *sampling_points = nullptr;
352         if (show_sampling_points)
353                  sampling_points = new QRectF[points_count];
354         QRectF *sampling_point = sampling_points;
355
356         int64_t sample_count = min(points_count, TracePaintBlockSize);
357         int64_t block_sample = 0;
358         float *sample_block = new float[TracePaintBlockSize];
359         segment->get_samples(start, start + sample_count, sample_block);
360
361         const int w = 2;
362         for (int64_t sample = start; sample != end; sample++, block_sample++) {
363
364                 if (block_sample == TracePaintBlockSize) {
365                         block_sample = 0;
366                         sample_count = min(points_count - sample, TracePaintBlockSize);
367                         segment->get_samples(sample, sample + sample_count, sample_block);
368                 }
369
370                 const float x = (sample / samples_per_pixel -
371                         pixels_offset) + left;
372
373                 *point++ = QPointF(x, y - sample_block[block_sample] * scale_);
374
375                 if (show_sampling_points)
376                         *sampling_point++ =
377                                 QRectF(x - (w / 2), y - sample_block[block_sample] * scale_ - (w / 2), w, w);
378         }
379         delete[] sample_block;
380
381         p.drawPolyline(points, points_count);
382
383         if (show_sampling_points) {
384                 p.setPen(SamplingPointColour);
385                 p.drawRects(sampling_points, points_count);
386                 delete[] sampling_points;
387         }
388
389         delete[] points;
390 }
391
392 void AnalogSignal::paint_envelope(QPainter &p,
393         const shared_ptr<pv::data::AnalogSegment> &segment,
394         int y, int left, const int64_t start, const int64_t end,
395         const double pixels_offset, const double samples_per_pixel)
396 {
397         using pv::data::AnalogSegment;
398
399         AnalogSegment::EnvelopeSection e;
400         segment->get_envelope_section(e, start, end, samples_per_pixel);
401
402         if (e.length < 2)
403                 return;
404
405         p.setPen(QPen(Qt::NoPen));
406         p.setBrush(base_->colour());
407
408         QRectF *const rects = new QRectF[e.length];
409         QRectF *rect = rects;
410
411         for (uint64_t sample = 0; sample < e.length - 1; sample++) {
412                 const float x = ((e.scale * sample + e.start) /
413                         samples_per_pixel - pixels_offset) + left;
414                 const AnalogSegment::EnvelopeSample *const s =
415                         e.samples + sample;
416
417                 // We overlap this sample with the next so that vertical
418                 // gaps do not appear during steep rising or falling edges
419                 const float b = y - max(s->max, (s + 1)->min) * scale_;
420                 const float t = y - min(s->min, (s + 1)->max) * scale_;
421
422                 float h = b - t;
423                 if (h >= 0.0f && h <= 1.0f)
424                         h = 1.0f;
425                 if (h <= 0.0f && h >= -1.0f)
426                         h = -1.0f;
427
428                 *rect++ = QRectF(x, t, 1.0f, h);
429         }
430
431         p.drawRects(rects, e.length);
432
433         delete[] rects;
434         delete[] e.samples;
435 }
436
437 void AnalogSignal::paint_logic_mid(QPainter &p, ViewItemPaintParams &pp)
438 {
439         QLineF *line;
440
441         vector< pair<int64_t, bool> > edges;
442
443         assert(base_);
444
445         const int y = get_visual_y();
446
447         if (!base_->enabled() || !base_->logic_data())
448                 return;
449
450         const int signal_margin =
451                 QFontMetrics(QApplication::font()).height() / 2;
452
453         const int ph = min(pos_vdivs_, 1) * div_height_;
454         const int nh = min(neg_vdivs_, 1) * div_height_;
455         const float high_offset = y - ph + signal_margin + 0.5f;
456         const float low_offset = y + nh - signal_margin - 0.5f;
457
458         const deque< shared_ptr<pv::data::LogicSegment> > &segments =
459                 base_->logic_data()->logic_segments();
460
461         if (segments.empty())
462                 return;
463
464         const shared_ptr<pv::data::LogicSegment> &segment =
465                 segments.front();
466
467         double samplerate = segment->samplerate();
468
469         // Show sample rate as 1Hz when it is unknown
470         if (samplerate == 0.0)
471                 samplerate = 1.0;
472
473         const double pixels_offset = pp.pixels_offset();
474         const pv::util::Timestamp& start_time = segment->start_time();
475         const int64_t last_sample = segment->get_sample_count() - 1;
476         const double samples_per_pixel = samplerate * pp.scale();
477         const double pixels_per_sample = 1 / samples_per_pixel;
478         const pv::util::Timestamp start = samplerate * (pp.offset() - start_time);
479         const pv::util::Timestamp end = start + samples_per_pixel * pp.width();
480
481         const int64_t start_sample = min(max(floor(start).convert_to<int64_t>(),
482                 (int64_t)0), last_sample);
483         const uint64_t end_sample = min(max(ceil(end).convert_to<int64_t>(),
484                 (int64_t)0), last_sample);
485
486         segment->get_subsampled_edges(edges, start_sample, end_sample,
487                 samples_per_pixel / LogicSignal::Oversampling, 0);
488         assert(edges.size() >= 2);
489
490         // Check whether we need to paint the sampling points
491         GlobalSettings settings;
492         const bool show_sampling_points =
493                 settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool() &&
494                 (samples_per_pixel < 0.25);
495
496         vector<QRectF> sampling_points;
497         float sampling_point_x = 0.0f;
498         int64_t sampling_point_sample = start_sample;
499         const int w = 2;
500
501         if (show_sampling_points) {
502                 sampling_points.reserve(end_sample - start_sample + 1);
503                 sampling_point_x = (edges.cbegin()->first / samples_per_pixel - pixels_offset) + pp.left();
504         }
505
506         // Paint the edges
507         const unsigned int edge_count = edges.size() - 2;
508         QLineF *const edge_lines = new QLineF[edge_count];
509         line = edge_lines;
510
511         for (auto i = edges.cbegin() + 1; i != edges.cend() - 1; i++) {
512                 const float x = ((*i).first / samples_per_pixel -
513                         pixels_offset) + pp.left();
514                 *line++ = QLineF(x, high_offset, x, low_offset);
515
516                 if (show_sampling_points)
517                         while (sampling_point_sample < (*i).first) {
518                                 const float y = (*i).second ? low_offset : high_offset;
519                                 sampling_points.emplace_back(
520                                         QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w));
521                                 sampling_point_sample++;
522                                 sampling_point_x += pixels_per_sample;
523                         };
524         }
525
526         // Calculate the sample points from the last edge to the end of the trace
527         if (show_sampling_points)
528                 while ((uint64_t)sampling_point_sample <= end_sample) {
529                         // Signal changed after the last edge, so the level is inverted
530                         const float y = (edges.cend() - 1)->second ? high_offset : low_offset;
531                         sampling_points.emplace_back(
532                                 QRectF(sampling_point_x - (w / 2), y - (w / 2), w, w));
533                         sampling_point_sample++;
534                         sampling_point_x += pixels_per_sample;
535                 };
536
537         p.setPen(LogicSignal::EdgeColour);
538         p.drawLines(edge_lines, edge_count);
539         delete[] edge_lines;
540
541         // Paint the caps
542         const unsigned int max_cap_line_count = edges.size();
543         QLineF *const cap_lines = new QLineF[max_cap_line_count];
544
545         p.setPen(LogicSignal::HighColour);
546         paint_logic_caps(p, cap_lines, edges, true, samples_per_pixel,
547                 pixels_offset, pp.left(), high_offset);
548         p.setPen(LogicSignal::LowColour);
549         paint_logic_caps(p, cap_lines, edges, false, samples_per_pixel,
550                 pixels_offset, pp.left(), low_offset);
551
552         delete[] cap_lines;
553
554         // Paint the sampling points
555         if (show_sampling_points) {
556                 p.setPen(SamplingPointColour);
557                 p.drawRects(sampling_points.data(), sampling_points.size());
558         }
559 }
560
561 void AnalogSignal::paint_logic_caps(QPainter &p, QLineF *const lines,
562         vector< pair<int64_t, bool> > &edges, bool level,
563         double samples_per_pixel, double pixels_offset, float x_offset,
564         float y_offset)
565 {
566         QLineF *line = lines;
567
568         for (auto i = edges.begin(); i != (edges.end() - 1); i++)
569                 if ((*i).second == level) {
570                         *line++ = QLineF(
571                                 ((*i).first / samples_per_pixel -
572                                         pixels_offset) + x_offset, y_offset,
573                                 ((*(i+1)).first / samples_per_pixel -
574                                         pixels_offset) + x_offset, y_offset);
575                 }
576
577         p.drawLines(lines, line - lines);
578 }
579
580 float AnalogSignal::get_resolution(int scale_index)
581 {
582         const float seq[] = {1.0f, 2.0f, 5.0f};
583
584         const int offset = numeric_limits<int>::max() / (2 * countof(seq));
585         const div_t d = div((int)(scale_index + countof(seq) * offset),
586                 countof(seq));
587
588         return powf(10.0f, d.quot - offset) * seq[d.rem];
589 }
590
591 void AnalogSignal::update_scale()
592 {
593         resolution_ = get_resolution(scale_index_);
594         scale_ = div_height_ / resolution_;
595 }
596
597 void AnalogSignal::update_conversion_widgets()
598 {
599         SignalBase::ConversionType conv_type = base_->get_conversion_type();
600
601         // Enable or disable widgets depending on conversion state
602         conv_threshold_cb_->setEnabled(conv_type != SignalBase::NoConversion);
603         display_type_cb_->setEnabled(conv_type != SignalBase::NoConversion);
604
605         conv_threshold_cb_->clear();
606
607         vector < pair<QString, int> > presets = base_->get_conversion_presets();
608
609         // Prevent the combo box from firing the "edit text changed" signal
610         // as that would involuntarily select the first entry
611         conv_threshold_cb_->blockSignals(true);
612
613         // Set available options depending on chosen conversion
614         for (pair<QString, int> preset : presets)
615                 conv_threshold_cb_->addItem(preset.first, preset.second);
616
617         map < QString, QVariant > options = base_->get_conversion_options();
618
619         if (conv_type == SignalBase::A2LConversionByTreshold) {
620                 const vector<double> thresholds = base_->get_conversion_thresholds(
621                                 SignalBase::A2LConversionByTreshold, true);
622                 conv_threshold_cb_->addItem(
623                                 QString("%1V").arg(QString::number(thresholds[0], 'f', 1)), -1);
624         }
625
626         if (conv_type == SignalBase::A2LConversionBySchmittTrigger) {
627                 const vector<double> thresholds = base_->get_conversion_thresholds(
628                                 SignalBase::A2LConversionBySchmittTrigger, true);
629                 conv_threshold_cb_->addItem(QString("%1V/%2V").arg(
630                                 QString::number(thresholds[0], 'f', 1),
631                                 QString::number(thresholds[1], 'f', 1)), -1);
632         }
633
634         int preset_id = base_->get_current_conversion_preset();
635         conv_threshold_cb_->setCurrentIndex(
636                         conv_threshold_cb_->findData(preset_id));
637
638         conv_threshold_cb_->blockSignals(false);
639 }
640
641 void AnalogSignal::perform_autoranging(bool keep_divs, bool force_update)
642 {
643         const deque< shared_ptr<pv::data::AnalogSegment> > &segments =
644                 base_->analog_data()->analog_segments();
645
646         if (segments.empty())
647                 return;
648
649         static double prev_min = 0, prev_max = 0;
650         double min = 0, max = 0;
651
652         for (shared_ptr<pv::data::AnalogSegment> segment : segments) {
653                 pair<double, double> mm = segment->get_min_max();
654                 min = std::min(min, mm.first);
655                 max = std::max(max, mm.second);
656         }
657
658         if ((min == prev_min) && (max == prev_max) && !force_update)
659                 return;
660
661         prev_min = min;
662         prev_max = max;
663
664         // If we're allowed to alter the div assignment...
665         if (!keep_divs) {
666                 // Use all divs for the positive range if there are no negative values
667                 if ((min == 0) && (neg_vdivs_ > 0)) {
668                         pos_vdivs_ += neg_vdivs_;
669                         neg_vdivs_ = 0;
670                 }
671
672                 // Split up the divs if there are negative values but no negative divs
673                 if ((min < 0) && (neg_vdivs_ == 0)) {
674                         neg_vdivs_ = pos_vdivs_ / 2;
675                         pos_vdivs_ -= neg_vdivs_;
676                 }
677         }
678
679         // If there is still no positive div when we need it, add one
680         // (this can happen when pos_vdivs==neg_vdivs==0)
681         if ((max > 0) && (pos_vdivs_ == 0)) {
682                 pos_vdivs_ = 1;
683                 owner_->extents_changed(false, true);
684         }
685
686         // If there is still no negative div when we need it, add one
687         // (this can happen when pos_vdivs was 0 or 1 when trying to split)
688         if ((min < 0) && (neg_vdivs_ == 0)) {
689                 neg_vdivs_ = 1;
690                 owner_->extents_changed(false, true);
691         }
692
693         double min_value_per_div;
694         if ((pos_vdivs_ > 0) && (neg_vdivs_ >  0))
695                 min_value_per_div = std::max(max / pos_vdivs_, -min / neg_vdivs_);
696         else if (pos_vdivs_ > 0)
697                 min_value_per_div = max / pos_vdivs_;
698         else
699                 min_value_per_div = -min / neg_vdivs_;
700
701         // Find first scale value that is bigger than the value we need
702         for (int i = MinScaleIndex; i < MaxScaleIndex; i++)
703                 if (get_resolution(i) > min_value_per_div) {
704                         scale_index_ = i;
705                         break;
706                 }
707
708         update_scale();
709 }
710
711 void AnalogSignal::populate_popup_form(QWidget *parent, QFormLayout *form)
712 {
713         // Add the standard options
714         Signal::populate_popup_form(parent, form);
715
716         QFormLayout *const layout = new QFormLayout;
717
718         // Add div-related settings
719         pvdiv_sb_ = new QSpinBox(parent);
720         pvdiv_sb_->setRange(0, MaximumVDivs);
721         pvdiv_sb_->setValue(pos_vdivs_);
722         connect(pvdiv_sb_, SIGNAL(valueChanged(int)),
723                 this, SLOT(on_pos_vdivs_changed(int)));
724         layout->addRow(tr("Number of pos vertical divs"), pvdiv_sb_);
725
726         nvdiv_sb_ = new QSpinBox(parent);
727         nvdiv_sb_->setRange(0, MaximumVDivs);
728         nvdiv_sb_->setValue(neg_vdivs_);
729         connect(nvdiv_sb_, SIGNAL(valueChanged(int)),
730                 this, SLOT(on_neg_vdivs_changed(int)));
731         layout->addRow(tr("Number of neg vertical divs"), nvdiv_sb_);
732
733         div_height_sb_ = new QSpinBox(parent);
734         div_height_sb_->setRange(20, 1000);
735         div_height_sb_->setSingleStep(5);
736         div_height_sb_->setSuffix(tr(" pixels"));
737         div_height_sb_->setValue(div_height_);
738         connect(div_height_sb_, SIGNAL(valueChanged(int)),
739                 this, SLOT(on_div_height_changed(int)));
740         layout->addRow(tr("Div height"), div_height_sb_);
741
742         // Add the vertical resolution
743         resolution_cb_ = new QComboBox(parent);
744
745         for (int i = MinScaleIndex; i < MaxScaleIndex; i++) {
746                 const QString label = QString("%1").arg(get_resolution(i));
747                 resolution_cb_->insertItem(0, label, QVariant(i));
748         }
749
750         int cur_idx = resolution_cb_->findData(QVariant(scale_index_));
751         resolution_cb_->setCurrentIndex(cur_idx);
752
753         connect(resolution_cb_, SIGNAL(currentIndexChanged(int)),
754                 this, SLOT(on_resolution_changed(int)));
755
756         QGridLayout *const vdiv_layout = new QGridLayout;
757         QLabel *const vdiv_unit = new QLabel(tr("V/div"));
758         vdiv_layout->addWidget(resolution_cb_, 0, 0);
759         vdiv_layout->addWidget(vdiv_unit, 0, 1);
760
761         layout->addRow(tr("Vertical resolution"), vdiv_layout);
762
763         // Add the autoranging checkbox
764         QCheckBox* autoranging_cb = new QCheckBox();
765         autoranging_cb->setCheckState(autoranging_ ? Qt::Checked : Qt::Unchecked);
766
767         connect(autoranging_cb, SIGNAL(stateChanged(int)),
768                 this, SLOT(on_autoranging_changed(int)));
769
770         layout->addRow(tr("Autoranging"), autoranging_cb);
771
772         // Add the conversion type dropdown
773         conversion_cb_ = new QComboBox();
774
775         conversion_cb_->addItem(tr("none"),
776                 SignalBase::NoConversion);
777         conversion_cb_->addItem(tr("to logic via threshold"),
778                 SignalBase::A2LConversionByTreshold);
779         conversion_cb_->addItem(tr("to logic via schmitt-trigger"),
780                 SignalBase::A2LConversionBySchmittTrigger);
781
782         cur_idx = conversion_cb_->findData(QVariant(base_->get_conversion_type()));
783         conversion_cb_->setCurrentIndex(cur_idx);
784
785         layout->addRow(tr("Conversion"), conversion_cb_);
786
787         connect(conversion_cb_, SIGNAL(currentIndexChanged(int)),
788                 this, SLOT(on_conversion_changed(int)));
789
790     // Add the conversion threshold settings
791     conv_threshold_cb_ = new QComboBox();
792     conv_threshold_cb_->setEditable(true);
793
794     layout->addRow(tr("Conversion threshold(s)"), conv_threshold_cb_);
795
796     connect(conv_threshold_cb_, SIGNAL(currentIndexChanged(int)),
797             this, SLOT(on_conv_threshold_changed(int)));
798     connect(conv_threshold_cb_, SIGNAL(editTextChanged(const QString)),
799             this, SLOT(on_conv_threshold_changed()));  // index will be -1
800
801         // Add the display type dropdown
802         display_type_cb_ = new QComboBox();
803
804         display_type_cb_->addItem(tr("analog"), DisplayAnalog);
805         display_type_cb_->addItem(tr("converted"), DisplayConverted);
806         display_type_cb_->addItem(tr("analog+converted"), DisplayBoth);
807
808         cur_idx = display_type_cb_->findData(QVariant(display_type_));
809         display_type_cb_->setCurrentIndex(cur_idx);
810
811         layout->addRow(tr("Show traces for"), display_type_cb_);
812
813         connect(display_type_cb_, SIGNAL(currentIndexChanged(int)),
814                 this, SLOT(on_display_type_changed(int)));
815
816         // Update the conversion widget contents and states
817         update_conversion_widgets();
818
819         form->addRow(layout);
820 }
821
822 void AnalogSignal::on_samples_added()
823 {
824         perform_autoranging(false, false);
825 }
826
827 void AnalogSignal::on_pos_vdivs_changed(int vdivs)
828 {
829         if (vdivs == pos_vdivs_)
830                 return;
831
832         pos_vdivs_ = vdivs;
833
834         // There has to be at least one div, positive or negative
835         if ((neg_vdivs_ == 0) && (pos_vdivs_ == 0)) {
836                 pos_vdivs_ = 1;
837                 if (pvdiv_sb_)
838                         pvdiv_sb_->setValue(pos_vdivs_);
839         }
840
841         if (autoranging_) {
842                 perform_autoranging(true, true);
843
844                 // It could be that a positive or negative div was added, so update
845                 if (pvdiv_sb_) {
846                         pvdiv_sb_->setValue(pos_vdivs_);
847                         nvdiv_sb_->setValue(neg_vdivs_);
848                 }
849         }
850
851         if (owner_) {
852                 // Call order is important, otherwise the lazy event handler won't work
853                 owner_->extents_changed(false, true);
854                 owner_->row_item_appearance_changed(false, true);
855         }
856 }
857
858 void AnalogSignal::on_neg_vdivs_changed(int vdivs)
859 {
860         if (vdivs == neg_vdivs_)
861                 return;
862
863         neg_vdivs_ = vdivs;
864
865         // There has to be at least one div, positive or negative
866         if ((neg_vdivs_ == 0) && (pos_vdivs_ == 0)) {
867                 pos_vdivs_ = 1;
868                 if (pvdiv_sb_)
869                         pvdiv_sb_->setValue(pos_vdivs_);
870         }
871
872         if (autoranging_) {
873                 perform_autoranging(true, true);
874
875                 // It could be that a positive or negative div was added, so update
876                 if (pvdiv_sb_) {
877                         pvdiv_sb_->setValue(pos_vdivs_);
878                         nvdiv_sb_->setValue(neg_vdivs_);
879                 }
880         }
881
882         if (owner_) {
883                 // Call order is important, otherwise the lazy event handler won't work
884                 owner_->extents_changed(false, true);
885                 owner_->row_item_appearance_changed(false, true);
886         }
887 }
888
889 void AnalogSignal::on_div_height_changed(int height)
890 {
891         div_height_ = height;
892         update_scale();
893
894         if (owner_) {
895                 // Call order is important, otherwise the lazy event handler won't work
896                 owner_->extents_changed(false, true);
897                 owner_->row_item_appearance_changed(false, true);
898         }
899 }
900
901 void AnalogSignal::on_resolution_changed(int index)
902 {
903         scale_index_ = resolution_cb_->itemData(index).toInt();
904         update_scale();
905
906         if (owner_)
907                 owner_->row_item_appearance_changed(false, true);
908 }
909
910 void AnalogSignal::on_autoranging_changed(int state)
911 {
912         autoranging_ = (state == Qt::Checked);
913
914         if (autoranging_)
915                 perform_autoranging(false, true);
916
917         if (owner_) {
918                 // Call order is important, otherwise the lazy event handler won't work
919                 owner_->extents_changed(false, true);
920                 owner_->row_item_appearance_changed(false, true);
921         }
922 }
923
924 void AnalogSignal::on_conversion_changed(int index)
925 {
926         SignalBase::ConversionType old_conv_type = base_->get_conversion_type();
927
928         SignalBase::ConversionType conv_type =
929                 (SignalBase::ConversionType)(conversion_cb_->itemData(index).toInt());
930
931         if (conv_type != old_conv_type) {
932                 base_->set_conversion_type(conv_type);
933                 update_conversion_widgets();
934
935                 if (owner_)
936                         owner_->row_item_appearance_changed(false, true);
937         }
938 }
939
940 void AnalogSignal::on_conv_threshold_changed(int index)
941 {
942         SignalBase::ConversionType conv_type = base_->get_conversion_type();
943
944         // Note: index is set to -1 if the text in the combo box matches none of
945         // the entries in the combo box
946
947         if ((index == -1) && (conv_threshold_cb_->currentText().length() == 0))
948                 return;
949
950         // The combo box entry with the custom value has user_data set to -1
951         const int user_data = conv_threshold_cb_->findText(
952                         conv_threshold_cb_->currentText());
953
954         const bool use_custom_thr = (index == -1) || (user_data == -1);
955
956         if (conv_type == SignalBase::A2LConversionByTreshold && use_custom_thr) {
957                 // Not one of the preset values, try to parse the combo box text
958                 // Note: Regex loosely based on
959                 // https://txt2re.com/index-c++.php3?s=0.1V&1&-13
960                 QString re1 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value
961                 QString re2 = "([a-zA-Z]*)"; // SI unit
962                 QRegExp regex(re1 + re2);
963
964                 const QString text = conv_threshold_cb_->currentText();
965                 if (!regex.exactMatch(text))
966                         return;  // String doesn't match the regex
967
968                 QStringList tokens = regex.capturedTexts();
969
970                 // For now, we simply assume that the unit is volt without modifiers
971                 const double thr = tokens.at(1).toDouble();
972
973                 // Only restart the conversion if the threshold was updated
974                 if (base_->set_conversion_option("threshold_value", thr))
975                         delayed_conversion_starter_.start();
976         }
977
978         if (conv_type == SignalBase::A2LConversionBySchmittTrigger && use_custom_thr) {
979                 // Not one of the preset values, try to parse the combo box text
980                 // Note: Regex loosely based on
981                 // https://txt2re.com/index-c++.php3?s=0.1V/0.2V&2&14&-22&3&15
982                 QString re1 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value
983                 QString re2 = "([a-zA-Z]*)"; // SI unit
984                 QString re3 = "\\/"; // Forward slash, not captured
985                 QString re4 = "([+-]?\\d*[\\.,]?\\d*)"; // Float value
986                 QString re5 = "([a-zA-Z]*)"; // SI unit
987                 QRegExp regex(re1 + re2 + re3 + re4 + re5);
988
989                 const QString text = conv_threshold_cb_->currentText();
990                 if (!regex.exactMatch(text))
991                         return;  // String doesn't match the regex
992
993                 QStringList tokens = regex.capturedTexts();
994
995                 // For now, we simply assume that the unit is volt without modifiers
996                 const double low_thr = tokens.at(1).toDouble();
997                 const double high_thr = tokens.at(3).toDouble();
998
999                 // Only restart the conversion if one of the options was updated
1000                 bool o1 = base_->set_conversion_option("threshold_value_low", low_thr);
1001                 bool o2 = base_->set_conversion_option("threshold_value_high", high_thr);
1002                 if (o1 || o2)
1003                         delayed_conversion_starter_.start();
1004         }
1005
1006         base_->set_conversion_preset(index);
1007
1008         // Immediately start the conversion if we're not asking for a delayed reaction
1009         if (!delayed_conversion_starter_.isActive())
1010                 base_->start_conversion();
1011 }
1012
1013 void AnalogSignal::on_delayed_conversion_starter()
1014 {
1015         base_->start_conversion();
1016 }
1017
1018 void AnalogSignal::on_display_type_changed(int index)
1019 {
1020         display_type_ = (DisplayType)(display_type_cb_->itemData(index).toInt());
1021
1022         if (owner_)
1023                 owner_->row_item_appearance_changed(false, true);
1024 }
1025
1026 } // namespace trace
1027 } // namespace views
1028 } // namespace pv