]> sigrok.org Git - pulseview.git/blob - pv/views/trace/decodetrace.cpp
Trace View: Add doxygen comments to some classes
[pulseview.git] / pv / views / trace / decodetrace.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 extern "C" {
21 #include <libsigrokdecode/libsigrokdecode.h>
22 }
23
24 #include <mutex>
25
26 #include <extdef.h>
27
28 #include <tuple>
29
30 #include <boost/functional/hash.hpp>
31
32 #include <QAction>
33 #include <QApplication>
34 #include <QComboBox>
35 #include <QFormLayout>
36 #include <QLabel>
37 #include <QMenu>
38 #include <QPushButton>
39 #include <QToolTip>
40
41 #include "decodetrace.hpp"
42 #include "view.hpp"
43 #include "viewport.hpp"
44
45 #include <pv/globalsettings.hpp>
46 #include <pv/session.hpp>
47 #include <pv/strnatcmp.hpp>
48 #include <pv/data/decodesignal.hpp>
49 #include <pv/data/decode/annotation.hpp>
50 #include <pv/data/decode/decoder.hpp>
51 #include <pv/data/logic.hpp>
52 #include <pv/data/logicsegment.hpp>
53 #include <pv/widgets/decodergroupbox.hpp>
54 #include <pv/widgets/decodermenu.hpp>
55
56 using std::all_of;
57 using std::make_pair;
58 using std::max;
59 using std::min;
60 using std::out_of_range;
61 using std::pair;
62 using std::shared_ptr;
63 using std::tie;
64 using std::vector;
65
66 using pv::data::decode::Annotation;
67 using pv::data::decode::Row;
68 using pv::data::DecodeChannel;
69 using pv::data::DecodeSignal;
70
71 namespace pv {
72 namespace views {
73 namespace trace {
74
75 const QColor DecodeTrace::DecodeColours[4] = {
76         QColor(0xEF, 0x29, 0x29),       // Red
77         QColor(0xFC, 0xE9, 0x4F),       // Yellow
78         QColor(0x8A, 0xE2, 0x34),       // Green
79         QColor(0x72, 0x9F, 0xCF)        // Blue
80 };
81
82 const QColor DecodeTrace::ErrorBgColour = QColor(0xEF, 0x29, 0x29);
83 const QColor DecodeTrace::NoDecodeColour = QColor(0x88, 0x8A, 0x85);
84
85 const int DecodeTrace::ArrowSize = 4;
86 const double DecodeTrace::EndCapWidth = 5;
87 const int DecodeTrace::RowTitleMargin = 10;
88 const int DecodeTrace::DrawPadding = 100;
89
90 const int DecodeTrace::MaxTraceUpdateRate = 1; // No more than 1 Hz
91
92 const QColor DecodeTrace::Colours[16] = {
93         QColor(0xEF, 0x29, 0x29),
94         QColor(0xF6, 0x6A, 0x32),
95         QColor(0xFC, 0xAE, 0x3E),
96         QColor(0xFB, 0xCA, 0x47),
97         QColor(0xFC, 0xE9, 0x4F),
98         QColor(0xCD, 0xF0, 0x40),
99         QColor(0x8A, 0xE2, 0x34),
100         QColor(0x4E, 0xDC, 0x44),
101         QColor(0x55, 0xD7, 0x95),
102         QColor(0x64, 0xD1, 0xD2),
103         QColor(0x72, 0x9F, 0xCF),
104         QColor(0xD4, 0x76, 0xC4),
105         QColor(0x9D, 0x79, 0xB9),
106         QColor(0xAD, 0x7F, 0xA8),
107         QColor(0xC2, 0x62, 0x9B),
108         QColor(0xD7, 0x47, 0x6F)
109 };
110
111 const QColor DecodeTrace::OutlineColours[16] = {
112         QColor(0x77, 0x14, 0x14),
113         QColor(0x7B, 0x35, 0x19),
114         QColor(0x7E, 0x57, 0x1F),
115         QColor(0x7D, 0x65, 0x23),
116         QColor(0x7E, 0x74, 0x27),
117         QColor(0x66, 0x78, 0x20),
118         QColor(0x45, 0x71, 0x1A),
119         QColor(0x27, 0x6E, 0x22),
120         QColor(0x2A, 0x6B, 0x4A),
121         QColor(0x32, 0x68, 0x69),
122         QColor(0x39, 0x4F, 0x67),
123         QColor(0x6A, 0x3B, 0x62),
124         QColor(0x4E, 0x3C, 0x5C),
125         QColor(0x56, 0x3F, 0x54),
126         QColor(0x61, 0x31, 0x4D),
127         QColor(0x6B, 0x23, 0x37)
128 };
129
130 DecodeTrace::DecodeTrace(pv::Session &session,
131         shared_ptr<data::SignalBase> signalbase, int index) :
132         Trace(signalbase),
133         session_(session),
134         row_height_(0),
135         max_visible_rows_(0),
136         delete_mapper_(this),
137         show_hide_mapper_(this)
138 {
139         decode_signal_ = dynamic_pointer_cast<data::DecodeSignal>(base_);
140
141         // Determine shortest string we want to see displayed in full
142         QFontMetrics m(QApplication::font());
143         min_useful_label_width_ = m.width("XX"); // e.g. two hex characters
144
145         base_->set_colour(DecodeColours[index % countof(DecodeColours)]);
146
147         connect(decode_signal_.get(), SIGNAL(new_annotations()),
148                 this, SLOT(on_new_annotations()));
149         connect(decode_signal_.get(), SIGNAL(decode_finished()),
150                 this, SLOT(on_decode_finished()));
151         connect(decode_signal_.get(), SIGNAL(channels_updated()),
152                 this, SLOT(on_channels_updated()));
153
154         connect(&delete_mapper_, SIGNAL(mapped(int)),
155                 this, SLOT(on_delete_decoder(int)));
156         connect(&show_hide_mapper_, SIGNAL(mapped(int)),
157                 this, SLOT(on_show_hide_decoder(int)));
158
159         connect(&delayed_trace_updater_, SIGNAL(timeout()),
160                 this, SLOT(on_delayed_trace_update()));
161         delayed_trace_updater_.setSingleShot(true);
162         delayed_trace_updater_.setInterval(1000 / MaxTraceUpdateRate);
163 }
164
165 bool DecodeTrace::enabled() const
166 {
167         return true;
168 }
169
170 shared_ptr<data::SignalBase> DecodeTrace::base() const
171 {
172         return base_;
173 }
174
175 pair<int, int> DecodeTrace::v_extents() const
176 {
177         const int row_height = (ViewItemPaintParams::text_height() * 6) / 4;
178
179         // Make an empty decode trace appear symmetrical
180         const int row_count = max(1, max_visible_rows_);
181
182         return make_pair(-row_height, row_height * row_count);
183 }
184
185 void DecodeTrace::paint_back(QPainter &p, ViewItemPaintParams &pp)
186 {
187         Trace::paint_back(p, pp);
188         paint_axis(p, pp, get_visual_y());
189 }
190
191 void DecodeTrace::paint_mid(QPainter &p, ViewItemPaintParams &pp)
192 {
193         const int text_height = ViewItemPaintParams::text_height();
194         row_height_ = (text_height * 6) / 4;
195         const int annotation_height = (text_height * 5) / 4;
196
197         const QString err = decode_signal_->error_message();
198         if (!err.isEmpty()) {
199                 draw_unresolved_period(
200                         p, annotation_height, pp.left(), pp.right());
201                 draw_error(p, err, pp);
202                 return;
203         }
204
205         // Set default pen to allow for text width calculation
206         p.setPen(Qt::black);
207
208         // Iterate through the rows
209         int y = get_visual_y();
210         pair<uint64_t, uint64_t> sample_range = get_sample_range(
211                 pp.left(), pp.right());
212
213         const vector<Row> rows = decode_signal_->visible_rows();
214
215         visible_rows_.clear();
216         for (const Row& row : rows) {
217                 // Cache the row title widths
218                 int row_title_width;
219                 try {
220                         row_title_width = row_title_widths_.at(row);
221                 } catch (out_of_range) {
222                         const int w = p.boundingRect(QRectF(), 0, row.title()).width() +
223                                 RowTitleMargin;
224                         row_title_widths_[row] = w;
225                         row_title_width = w;
226                 }
227
228                 // Determine the row's color
229                 size_t base_colour = 0x13579BDF;
230                 boost::hash_combine(base_colour, this);
231                 boost::hash_combine(base_colour, row.decoder());
232                 boost::hash_combine(base_colour, row.row());
233                 base_colour >>= 16;
234
235                 vector<Annotation> annotations;
236                 decode_signal_->get_annotation_subset(annotations, row,
237                         sample_range.first, sample_range.second);
238                 if (!annotations.empty()) {
239                         draw_annotations(annotations, p, annotation_height, pp, y,
240                                 base_colour, row_title_width);
241
242                         y += row_height_;
243
244                         visible_rows_.push_back(row);
245                 }
246         }
247
248         // Draw the hatching
249         draw_unresolved_period(p, annotation_height, pp.left(), pp.right());
250
251         if ((int)visible_rows_.size() > max_visible_rows_) {
252                 max_visible_rows_ = (int)visible_rows_.size();
253
254                 // Call order is important, otherwise the lazy event handler won't work
255                 owner_->extents_changed(false, true);
256                 owner_->row_item_appearance_changed(false, true);
257         }
258 }
259
260 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
261 {
262         assert(row_height_);
263
264         for (size_t i = 0; i < visible_rows_.size(); i++) {
265                 const int y = i * row_height_ + get_visual_y();
266
267                 p.setPen(QPen(Qt::NoPen));
268                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
269
270                 if (i != 0) {
271                         const QPointF points[] = {
272                                 QPointF(pp.left(), y - ArrowSize),
273                                 QPointF(pp.left() + ArrowSize, y),
274                                 QPointF(pp.left(), y + ArrowSize)
275                         };
276                         p.drawPolygon(points, countof(points));
277                 }
278
279                 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
280                         pp.right() - pp.left(), row_height_);
281                 const QString h(visible_rows_[i].title());
282                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
283                         Qt::TextDontClip;
284
285                 // Draw the outline
286                 p.setPen(QApplication::palette().color(QPalette::Base));
287                 for (int dx = -1; dx <= 1; dx++)
288                         for (int dy = -1; dy <= 1; dy++)
289                                 if (dx != 0 && dy != 0)
290                                         p.drawText(r.translated(dx, dy), f, h);
291
292                 // Draw the text
293                 p.setPen(QApplication::palette().color(QPalette::WindowText));
294                 p.drawText(r, f, h);
295         }
296 }
297
298 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
299 {
300         using pv::data::decode::Decoder;
301
302         assert(form);
303
304         // Add the standard options
305         Trace::populate_popup_form(parent, form);
306
307         // Add the decoder options
308         bindings_.clear();
309         channel_id_map_.clear();
310         init_state_map_.clear();
311         decoder_forms_.clear();
312
313         const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
314
315         if (stack.empty()) {
316                 QLabel *const l = new QLabel(
317                         tr("<p><i>No decoders in the stack</i></p>"));
318                 l->setAlignment(Qt::AlignCenter);
319                 form->addRow(l);
320         } else {
321                 auto iter = stack.cbegin();
322                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
323                         shared_ptr<Decoder> dec(*iter);
324                         create_decoder_form(i, dec, parent, form);
325                 }
326
327                 form->addRow(new QLabel(
328                         tr("<i>* Required channels</i>"), parent));
329         }
330
331         // Add stacking button
332         pv::widgets::DecoderMenu *const decoder_menu =
333                 new pv::widgets::DecoderMenu(parent);
334         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
335                 this, SLOT(on_stack_decoder(srd_decoder*)));
336
337         QPushButton *const stack_button =
338                 new QPushButton(tr("Stack Decoder"), parent);
339         stack_button->setMenu(decoder_menu);
340         stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
341
342         QHBoxLayout *stack_button_box = new QHBoxLayout;
343         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
344         form->addRow(stack_button_box);
345 }
346
347 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
348 {
349         QMenu *const menu = Trace::create_context_menu(parent);
350
351         menu->addSeparator();
352
353         QAction *const del = new QAction(tr("Delete"), this);
354         del->setShortcuts(QKeySequence::Delete);
355         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
356         menu->addAction(del);
357
358         return menu;
359 }
360
361 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
362                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
363                 size_t base_colour, int row_title_width)
364 {
365         using namespace pv::data::decode;
366
367         vector<Annotation> a_block;
368         int p_end = INT_MIN;
369
370         double samples_per_pixel, pixels_offset;
371         tie(pixels_offset, samples_per_pixel) =
372                 get_pixels_offset_samples_per_pixel();
373
374         // Sort the annotations by start sample so that decoders
375         // can't confuse us by creating annotations out of order
376         stable_sort(annotations.begin(), annotations.end(),
377                 [](const Annotation &a, const Annotation &b) {
378                         return a.start_sample() < b.start_sample(); });
379
380         // Gather all annotations that form a visual "block" and draw them as such
381         for (const Annotation &a : annotations) {
382
383                 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
384                 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
385                 const int a_width = a_end - a_start;
386
387                 const int delta = a_end - p_end;
388
389                 bool a_is_separate = false;
390
391                 // Annotation wider than the threshold for a useful label width?
392                 if (a_width >= min_useful_label_width_) {
393                         for (const QString &ann_text : a.annotations()) {
394                                 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
395                                 // Annotation wide enough to fit a label? Don't put it in a block then
396                                 if (w <= a_width) {
397                                         a_is_separate = true;
398                                         break;
399                                 }
400                         }
401                 }
402
403                 // Were the previous and this annotation more than a pixel apart?
404                 if ((abs(delta) > 1) || a_is_separate) {
405                         // Block was broken, draw annotations that form the current block
406                         if (a_block.size() == 1) {
407                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
408                                         row_title_width);
409                         }
410                         else
411                                 draw_annotation_block(a_block, p, h, y, base_colour);
412
413                         a_block.clear();
414                 }
415
416                 if (a_is_separate) {
417                         draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
418                         // Next annotation must start a new block. delta will be > 1
419                         // because we set p_end to INT_MIN but that's okay since
420                         // a_block will be empty, so nothing will be drawn
421                         p_end = INT_MIN;
422                 } else {
423                         a_block.push_back(a);
424                         p_end = a_end;
425                 }
426         }
427
428         if (a_block.size() == 1)
429                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
430                         row_title_width);
431         else
432                 draw_annotation_block(a_block, p, h, y, base_colour);
433 }
434
435 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
436         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
437         size_t base_colour, int row_title_width) const
438 {
439         double samples_per_pixel, pixels_offset;
440         tie(pixels_offset, samples_per_pixel) =
441                 get_pixels_offset_samples_per_pixel();
442
443         const double start = a.start_sample() / samples_per_pixel -
444                 pixels_offset;
445         const double end = a.end_sample() / samples_per_pixel - pixels_offset;
446
447         const size_t colour = (base_colour + a.format()) % countof(Colours);
448         p.setPen(OutlineColours[colour]);
449         p.setBrush(Colours[colour]);
450
451         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
452                 return;
453
454         if (a.start_sample() == a.end_sample())
455                 draw_instant(a, p, h, start, y);
456         else
457                 draw_range(a, p, h, start, end, y, pp, row_title_width);
458 }
459
460 void DecodeTrace::draw_annotation_block(
461         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
462         int y, size_t base_colour) const
463 {
464         using namespace pv::data::decode;
465
466         if (annotations.empty())
467                 return;
468
469         double samples_per_pixel, pixels_offset;
470         tie(pixels_offset, samples_per_pixel) =
471                 get_pixels_offset_samples_per_pixel();
472
473         const double start = annotations.front().start_sample() /
474                 samples_per_pixel - pixels_offset;
475         const double end = annotations.back().end_sample() /
476                 samples_per_pixel - pixels_offset;
477
478         const double top = y + .5 - h / 2;
479         const double bottom = y + .5 + h / 2;
480
481         const size_t colour = (base_colour + annotations.front().format()) %
482                 countof(Colours);
483
484         // Check if all annotations are of the same type (i.e. we can use one color)
485         // or if we should use a neutral color (i.e. gray)
486         const int format = annotations.front().format();
487         const bool single_format = all_of(
488                 annotations.begin(), annotations.end(),
489                 [&](const Annotation &a) { return a.format() == format; });
490
491         const QRectF rect(start, top, end - start, bottom - top);
492         const int r = h / 4;
493
494         p.setPen(QPen(Qt::NoPen));
495         p.setBrush(Qt::white);
496         p.drawRoundedRect(rect, r, r);
497
498         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
499         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
500                 Qt::Dense4Pattern));
501         p.drawRoundedRect(rect, r, r);
502 }
503
504 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
505         int h, double x, int y) const
506 {
507         const QString text = a.annotations().empty() ?
508                 QString() : a.annotations().back();
509         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
510                 0.0) + h;
511         const QRectF rect(x - w / 2, y - h / 2, w, h);
512
513         p.drawRoundedRect(rect, h / 2, h / 2);
514
515         p.setPen(Qt::black);
516         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
517 }
518
519 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
520         int h, double start, double end, int y, const ViewItemPaintParams &pp,
521         int row_title_width) const
522 {
523         const double top = y + .5 - h / 2;
524         const double bottom = y + .5 + h / 2;
525         const vector<QString> annotations = a.annotations();
526
527         // If the two ends are within 1 pixel, draw a vertical line
528         if (start + 1.0 > end) {
529                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
530                 return;
531         }
532
533         const double cap_width = min((end - start) / 4, EndCapWidth);
534
535         QPointF pts[] = {
536                 QPointF(start, y + .5f),
537                 QPointF(start + cap_width, top),
538                 QPointF(end - cap_width, top),
539                 QPointF(end, y + .5f),
540                 QPointF(end - cap_width, bottom),
541                 QPointF(start + cap_width, bottom)
542         };
543
544         p.drawConvexPolygon(pts, countof(pts));
545
546         if (annotations.empty())
547                 return;
548
549         const int ann_start = start + cap_width;
550         const int ann_end = end - cap_width;
551
552         const int real_start = max(ann_start, pp.left() + row_title_width);
553         const int real_end = min(ann_end, pp.right());
554         const int real_width = real_end - real_start;
555
556         QRectF rect(real_start, y - h / 2, real_width, h);
557         if (rect.width() <= 4)
558                 return;
559
560         p.setPen(Qt::black);
561
562         // Try to find an annotation that will fit
563         QString best_annotation;
564         int best_width = 0;
565
566         for (const QString &a : annotations) {
567                 const int w = p.boundingRect(QRectF(), 0, a).width();
568                 if (w <= rect.width() && w > best_width)
569                         best_annotation = a, best_width = w;
570         }
571
572         if (best_annotation.isEmpty())
573                 best_annotation = annotations.back();
574
575         // If not ellide the last in the list
576         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
577                 best_annotation, Qt::ElideRight, rect.width()));
578 }
579
580 void DecodeTrace::draw_error(QPainter &p, const QString &message,
581         const ViewItemPaintParams &pp)
582 {
583         const int y = get_visual_y();
584
585         p.setPen(ErrorBgColour.darker());
586         p.setBrush(ErrorBgColour);
587
588         const QRectF bounding_rect =
589                 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
590         const QRectF text_rect = p.boundingRect(bounding_rect,
591                 Qt::AlignCenter, message);
592         const float r = text_rect.height() / 4;
593
594         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
595                 Qt::AbsoluteSize);
596
597         p.setPen(Qt::black);
598         p.drawText(text_rect, message);
599 }
600
601 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
602 {
603         using namespace pv::data;
604         using pv::data::decode::Decoder;
605
606         double samples_per_pixel, pixels_offset;
607
608         const int64_t sample_count = decode_signal_->get_working_sample_count();
609         if (sample_count == 0)
610                 return;
611
612         const int64_t samples_decoded = decode_signal_->get_decoded_sample_count();
613         if (sample_count == samples_decoded)
614                 return;
615
616         const int y = get_visual_y();
617
618         tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
619
620         const double start = max(samples_decoded /
621                 samples_per_pixel - pixels_offset, left - 1.0);
622         const double end = min(sample_count / samples_per_pixel -
623                 pixels_offset, right + 1.0);
624         const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
625
626         p.setPen(QPen(Qt::NoPen));
627         p.setBrush(Qt::white);
628         p.drawRect(no_decode_rect);
629
630         p.setPen(NoDecodeColour);
631         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
632         p.drawRect(no_decode_rect);
633 }
634
635 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
636 {
637         assert(owner_);
638
639         const View *view = owner_->view();
640         assert(view);
641
642         const double scale = view->scale();
643         assert(scale > 0);
644
645         const double pixels_offset =
646                 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
647
648         double samplerate = decode_signal_->samplerate();
649
650         // Show sample rate as 1Hz when it is unknown
651         if (samplerate == 0.0)
652                 samplerate = 1.0;
653
654         return make_pair(pixels_offset, samplerate * scale);
655 }
656
657 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
658         int x_start, int x_end) const
659 {
660         double samples_per_pixel, pixels_offset;
661         tie(pixels_offset, samples_per_pixel) =
662                 get_pixels_offset_samples_per_pixel();
663
664         const uint64_t start = (uint64_t)max(
665                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
666         const uint64_t end = (uint64_t)max(
667                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
668
669         return make_pair(start, end);
670 }
671
672 int DecodeTrace::get_row_at_point(const QPoint &point)
673 {
674         if (!row_height_)
675                 return -1;
676
677         const int y = (point.y() - get_visual_y() + row_height_ / 2);
678
679         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
680         if (y < 0)
681                 return -1;
682
683         const int row = y / row_height_;
684
685         if (row >= (int)visible_rows_.size())
686                 return -1;
687
688         return row;
689 }
690
691 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
692 {
693         using namespace pv::data::decode;
694
695         if (!enabled())
696                 return QString();
697
698         const pair<uint64_t, uint64_t> sample_range =
699                 get_sample_range(point.x(), point.x() + 1);
700         const int row = get_row_at_point(point);
701         if (row < 0)
702                 return QString();
703
704         vector<pv::data::decode::Annotation> annotations;
705
706         decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
707                 sample_range.first, sample_range.second);
708
709         return (annotations.empty()) ?
710                 QString() : annotations[0].annotations().front();
711 }
712
713 void DecodeTrace::hover_point_changed(const QPoint &hp)
714 {
715         assert(owner_);
716
717         const View *const view = owner_->view();
718         assert(view);
719
720         if (hp.x() == 0) {
721                 QToolTip::hideText();
722                 return;
723         }
724
725         QString ann = get_annotation_at_point(hp);
726
727         assert(view);
728
729         if (!row_height_ || ann.isEmpty()) {
730                 QToolTip::hideText();
731                 return;
732         }
733
734         const int hover_row = get_row_at_point(hp);
735
736         QFontMetrics m(QToolTip::font());
737         const QRect text_size = m.boundingRect(QRect(), 0, ann);
738
739         // This is OS-specific and unfortunately we can't query it, so
740         // use an approximation to at least try to minimize the error.
741         const int padding = 8;
742
743         // Make sure the tool tip doesn't overlap with the mouse cursor.
744         // If it did, the tool tip would constantly hide and re-appear.
745         // We also push it up by one row so that it appears above the
746         // decode trace, not below.
747         QPoint p = hp;
748         p.setX(hp.x() - (text_size.width() / 2) - padding);
749
750         p.setY(get_visual_y() - (row_height_ / 2) +
751                 (hover_row * row_height_) -
752                 row_height_ - text_size.height() - padding);
753
754         QToolTip::showText(view->viewport()->mapToGlobal(p), ann);
755 }
756
757 void DecodeTrace::create_decoder_form(int index,
758         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
759         QFormLayout *form)
760 {
761         GlobalSettings settings;
762
763         assert(dec);
764         const srd_decoder *const decoder = dec->decoder();
765         assert(decoder);
766
767         const bool decoder_deletable = index > 0;
768
769         pv::widgets::DecoderGroupBox *const group =
770                 new pv::widgets::DecoderGroupBox(
771                         QString::fromUtf8(decoder->name),
772                         tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
773                                 QString::fromUtf8(decoder->desc)),
774                         nullptr, decoder_deletable);
775         group->set_decoder_visible(dec->shown());
776
777         if (decoder_deletable) {
778                 delete_mapper_.setMapping(group, index);
779                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
780         }
781
782         show_hide_mapper_.setMapping(group, index);
783         connect(group, SIGNAL(show_hide_decoder()),
784                 &show_hide_mapper_, SLOT(map()));
785
786         QFormLayout *const decoder_form = new QFormLayout;
787         group->add_layout(decoder_form);
788
789         const vector<DecodeChannel> channels = decode_signal_->get_channels();
790
791         // Add the channels
792         for (DecodeChannel ch : channels) {
793                 // Ignore channels not part of the decoder we create the form for
794                 if (ch.decoder_ != dec)
795                         continue;
796
797                 QComboBox *const combo = create_channel_selector(parent, &ch);
798                 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
799
800                 channel_id_map_[combo] = ch.id;
801                 init_state_map_[combo_init_state] = ch.id;
802
803                 connect(combo, SIGNAL(currentIndexChanged(int)),
804                         this, SLOT(on_channel_selected(int)));
805                 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
806                         this, SLOT(on_init_state_changed(int)));
807
808                 QHBoxLayout *const hlayout = new QHBoxLayout;
809                 hlayout->addWidget(combo);
810                 hlayout->addWidget(combo_init_state);
811
812                 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
813                         combo_init_state->hide();
814
815                 const QString required_flag = ch.is_optional ? QString() : QString("*");
816                 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
817                         .arg(ch.name, ch.desc, required_flag), hlayout);
818         }
819
820         // Add the options
821         shared_ptr<binding::Decoder> binding(
822                 new binding::Decoder(decode_signal_, dec));
823         binding->add_properties_to_form(decoder_form, true);
824
825         bindings_.push_back(binding);
826
827         form->addRow(group);
828         decoder_forms_.push_back(group);
829 }
830
831 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
832 {
833         const auto sigs(session_.signalbases());
834
835         // Sort signals in natural order
836         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
837         sort(sig_list.begin(), sig_list.end(),
838                 [](const shared_ptr<data::SignalBase> &a,
839                 const shared_ptr<data::SignalBase> &b) {
840                         return strnatcasecmp(a->name().toStdString(),
841                                 b->name().toStdString()) < 0; });
842
843         QComboBox *selector = new QComboBox(parent);
844
845         selector->addItem("-", qVariantFromValue((void*)nullptr));
846
847         if (!ch->assigned_signal)
848                 selector->setCurrentIndex(0);
849
850         for (const shared_ptr<data::SignalBase> &b : sig_list) {
851                 assert(b);
852                 if (b->logic_data() && b->enabled()) {
853                         selector->addItem(b->name(),
854                                 qVariantFromValue((void*)b.get()));
855
856                         if (ch->assigned_signal == b.get())
857                                 selector->setCurrentIndex(selector->count() - 1);
858                 }
859         }
860
861         return selector;
862 }
863
864 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
865         const DecodeChannel *ch)
866 {
867         QComboBox *selector = new QComboBox(parent);
868
869         selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
870         selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
871         selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
872
873         selector->setCurrentIndex(ch->initial_pin_state);
874
875         selector->setToolTip("Initial (assumed) pin value before the first sample");
876
877         return selector;
878 }
879
880 void DecodeTrace::on_new_annotations()
881 {
882         if (!delayed_trace_updater_.isActive())
883                 delayed_trace_updater_.start();
884 }
885
886 void DecodeTrace::on_delayed_trace_update()
887 {
888         if (owner_)
889                 owner_->row_item_appearance_changed(false, true);
890 }
891
892 void DecodeTrace::on_decode_finished()
893 {
894         if (owner_)
895                 owner_->row_item_appearance_changed(false, true);
896 }
897
898 void DecodeTrace::delete_pressed()
899 {
900         on_delete();
901 }
902
903 void DecodeTrace::on_delete()
904 {
905         session_.remove_decode_signal(decode_signal_);
906 }
907
908 void DecodeTrace::on_channel_selected(int)
909 {
910         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
911
912         // Determine signal that was selected
913         const data::SignalBase *signal =
914                 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
915
916         // Determine decode channel ID this combo box is the channel selector for
917         const uint16_t id = channel_id_map_.at(cb);
918
919         decode_signal_->assign_signal(id, signal);
920 }
921
922 void DecodeTrace::on_channels_updated()
923 {
924         if (owner_)
925                 owner_->row_item_appearance_changed(false, true);
926 }
927
928 void DecodeTrace::on_init_state_changed(int)
929 {
930         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
931
932         // Determine inital pin state that was selected
933         int init_state = cb->itemData(cb->currentIndex()).value<int>();
934
935         // Determine decode channel ID this combo box is the channel selector for
936         const uint16_t id = init_state_map_.at(cb);
937
938         decode_signal_->set_initial_pin_state(id, init_state);
939 }
940
941 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
942 {
943         decode_signal_->stack_decoder(decoder);
944
945         create_popup_form();
946 }
947
948 void DecodeTrace::on_delete_decoder(int index)
949 {
950         decode_signal_->remove_decoder(index);
951
952         // Force re-calculation of the trace height, see paint_mid()
953         max_visible_rows_ = 0;
954         owner_->extents_changed(false, true);
955
956         // Update the popup
957         create_popup_form();
958 }
959
960 void DecodeTrace::on_show_hide_decoder(int index)
961 {
962         const bool state = decode_signal_->toggle_decoder_visibility(index);
963
964         assert(index < (int)decoder_forms_.size());
965         decoder_forms_[index]->set_decoder_visible(state);
966
967         if (!state) {
968                 // Force re-calculation of the trace height, see paint_mid()
969                 max_visible_rows_ = 0;
970                 owner_->extents_changed(false, true);
971         }
972
973         if (owner_)
974                 owner_->row_item_appearance_changed(false, true);
975 }
976
977 } // namespace trace
978 } // namespace views
979 } // namespace pv