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