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