]> sigrok.org Git - pulseview.git/blob - pv/views/trace/decodetrace.cpp
e6b977ae01952ea1d22f377e8898b060c6fc3c1c
[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                 owner_->extents_changed(false, true);
253
254         // Update the maximum row count if needed
255         max_visible_rows_ = max(max_visible_rows_, (int)visible_rows_.size());
256 }
257
258 void DecodeTrace::paint_fore(QPainter &p, ViewItemPaintParams &pp)
259 {
260         assert(row_height_);
261
262         for (size_t i = 0; i < visible_rows_.size(); i++) {
263                 const int y = i * row_height_ + get_visual_y();
264
265                 p.setPen(QPen(Qt::NoPen));
266                 p.setBrush(QApplication::palette().brush(QPalette::WindowText));
267
268                 if (i != 0) {
269                         const QPointF points[] = {
270                                 QPointF(pp.left(), y - ArrowSize),
271                                 QPointF(pp.left() + ArrowSize, y),
272                                 QPointF(pp.left(), y + ArrowSize)
273                         };
274                         p.drawPolygon(points, countof(points));
275                 }
276
277                 const QRect r(pp.left() + ArrowSize * 2, y - row_height_ / 2,
278                         pp.right() - pp.left(), row_height_);
279                 const QString h(visible_rows_[i].title());
280                 const int f = Qt::AlignLeft | Qt::AlignVCenter |
281                         Qt::TextDontClip;
282
283                 // Draw the outline
284                 p.setPen(QApplication::palette().color(QPalette::Base));
285                 for (int dx = -1; dx <= 1; dx++)
286                         for (int dy = -1; dy <= 1; dy++)
287                                 if (dx != 0 && dy != 0)
288                                         p.drawText(r.translated(dx, dy), f, h);
289
290                 // Draw the text
291                 p.setPen(QApplication::palette().color(QPalette::WindowText));
292                 p.drawText(r, f, h);
293         }
294 }
295
296 void DecodeTrace::populate_popup_form(QWidget *parent, QFormLayout *form)
297 {
298         using pv::data::decode::Decoder;
299
300         assert(form);
301
302         // Add the standard options
303         Trace::populate_popup_form(parent, form);
304
305         // Add the decoder options
306         bindings_.clear();
307         channel_id_map_.clear();
308         init_state_map_.clear();
309         decoder_forms_.clear();
310
311         const vector< shared_ptr<Decoder> > &stack = decode_signal_->decoder_stack();
312
313         if (stack.empty()) {
314                 QLabel *const l = new QLabel(
315                         tr("<p><i>No decoders in the stack</i></p>"));
316                 l->setAlignment(Qt::AlignCenter);
317                 form->addRow(l);
318         } else {
319                 auto iter = stack.cbegin();
320                 for (int i = 0; i < (int)stack.size(); i++, iter++) {
321                         shared_ptr<Decoder> dec(*iter);
322                         create_decoder_form(i, dec, parent, form);
323                 }
324
325                 form->addRow(new QLabel(
326                         tr("<i>* Required channels</i>"), parent));
327         }
328
329         // Add stacking button
330         pv::widgets::DecoderMenu *const decoder_menu =
331                 new pv::widgets::DecoderMenu(parent);
332         connect(decoder_menu, SIGNAL(decoder_selected(srd_decoder*)),
333                 this, SLOT(on_stack_decoder(srd_decoder*)));
334
335         QPushButton *const stack_button =
336                 new QPushButton(tr("Stack Decoder"), parent);
337         stack_button->setMenu(decoder_menu);
338         stack_button->setToolTip(tr("Stack a higher-level decoder on top of this one"));
339
340         QHBoxLayout *stack_button_box = new QHBoxLayout;
341         stack_button_box->addWidget(stack_button, 0, Qt::AlignRight);
342         form->addRow(stack_button_box);
343 }
344
345 QMenu* DecodeTrace::create_context_menu(QWidget *parent)
346 {
347         QMenu *const menu = Trace::create_context_menu(parent);
348
349         menu->addSeparator();
350
351         QAction *const del = new QAction(tr("Delete"), this);
352         del->setShortcuts(QKeySequence::Delete);
353         connect(del, SIGNAL(triggered()), this, SLOT(on_delete()));
354         menu->addAction(del);
355
356         return menu;
357 }
358
359 void DecodeTrace::draw_annotations(vector<pv::data::decode::Annotation> annotations,
360                 QPainter &p, int h, const ViewItemPaintParams &pp, int y,
361                 size_t base_colour, int row_title_width)
362 {
363         using namespace pv::data::decode;
364
365         vector<Annotation> a_block;
366         int p_end = INT_MIN;
367
368         double samples_per_pixel, pixels_offset;
369         tie(pixels_offset, samples_per_pixel) =
370                 get_pixels_offset_samples_per_pixel();
371
372         // Sort the annotations by start sample so that decoders
373         // can't confuse us by creating annotations out of order
374         stable_sort(annotations.begin(), annotations.end(),
375                 [](const Annotation &a, const Annotation &b) {
376                         return a.start_sample() < b.start_sample(); });
377
378         // Gather all annotations that form a visual "block" and draw them as such
379         for (const Annotation &a : annotations) {
380
381                 const int a_start = a.start_sample() / samples_per_pixel - pixels_offset;
382                 const int a_end = a.end_sample() / samples_per_pixel - pixels_offset;
383                 const int a_width = a_end - a_start;
384
385                 const int delta = a_end - p_end;
386
387                 bool a_is_separate = false;
388
389                 // Annotation wider than the threshold for a useful label width?
390                 if (a_width >= min_useful_label_width_) {
391                         for (const QString &ann_text : a.annotations()) {
392                                 const int w = p.boundingRect(QRectF(), 0, ann_text).width();
393                                 // Annotation wide enough to fit a label? Don't put it in a block then
394                                 if (w <= a_width) {
395                                         a_is_separate = true;
396                                         break;
397                                 }
398                         }
399                 }
400
401                 // Were the previous and this annotation more than a pixel apart?
402                 if ((abs(delta) > 1) || a_is_separate) {
403                         // Block was broken, draw annotations that form the current block
404                         if (a_block.size() == 1) {
405                                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
406                                         row_title_width);
407                         }
408                         else
409                                 draw_annotation_block(a_block, p, h, y, base_colour);
410
411                         a_block.clear();
412                 }
413
414                 if (a_is_separate) {
415                         draw_annotation(a, p, h, pp, y, base_colour, row_title_width);
416                         // Next annotation must start a new block. delta will be > 1
417                         // because we set p_end to INT_MIN but that's okay since
418                         // a_block will be empty, so nothing will be drawn
419                         p_end = INT_MIN;
420                 } else {
421                         a_block.push_back(a);
422                         p_end = a_end;
423                 }
424         }
425
426         if (a_block.size() == 1)
427                 draw_annotation(a_block.front(), p, h, pp, y, base_colour,
428                         row_title_width);
429         else
430                 draw_annotation_block(a_block, p, h, y, base_colour);
431 }
432
433 void DecodeTrace::draw_annotation(const pv::data::decode::Annotation &a,
434         QPainter &p, int h, const ViewItemPaintParams &pp, int y,
435         size_t base_colour, int row_title_width) const
436 {
437         double samples_per_pixel, pixels_offset;
438         tie(pixels_offset, samples_per_pixel) =
439                 get_pixels_offset_samples_per_pixel();
440
441         const double start = a.start_sample() / samples_per_pixel -
442                 pixels_offset;
443         const double end = a.end_sample() / samples_per_pixel - pixels_offset;
444
445         const size_t colour = (base_colour + a.format()) % countof(Colours);
446         p.setPen(OutlineColours[colour]);
447         p.setBrush(Colours[colour]);
448
449         if (start > pp.right() + DrawPadding || end < pp.left() - DrawPadding)
450                 return;
451
452         if (a.start_sample() == a.end_sample())
453                 draw_instant(a, p, h, start, y);
454         else
455                 draw_range(a, p, h, start, end, y, pp, row_title_width);
456 }
457
458 void DecodeTrace::draw_annotation_block(
459         vector<pv::data::decode::Annotation> annotations, QPainter &p, int h,
460         int y, size_t base_colour) const
461 {
462         using namespace pv::data::decode;
463
464         if (annotations.empty())
465                 return;
466
467         double samples_per_pixel, pixels_offset;
468         tie(pixels_offset, samples_per_pixel) =
469                 get_pixels_offset_samples_per_pixel();
470
471         const double start = annotations.front().start_sample() /
472                 samples_per_pixel - pixels_offset;
473         const double end = annotations.back().end_sample() /
474                 samples_per_pixel - pixels_offset;
475
476         const double top = y + .5 - h / 2;
477         const double bottom = y + .5 + h / 2;
478
479         const size_t colour = (base_colour + annotations.front().format()) %
480                 countof(Colours);
481
482         // Check if all annotations are of the same type (i.e. we can use one color)
483         // or if we should use a neutral color (i.e. gray)
484         const int format = annotations.front().format();
485         const bool single_format = all_of(
486                 annotations.begin(), annotations.end(),
487                 [&](const Annotation &a) { return a.format() == format; });
488
489         const QRectF rect(start, top, end - start, bottom - top);
490         const int r = h / 4;
491
492         p.setPen(QPen(Qt::NoPen));
493         p.setBrush(Qt::white);
494         p.drawRoundedRect(rect, r, r);
495
496         p.setPen((single_format ? OutlineColours[colour] : Qt::gray));
497         p.setBrush(QBrush((single_format ? Colours[colour] : Qt::gray),
498                 Qt::Dense4Pattern));
499         p.drawRoundedRect(rect, r, r);
500 }
501
502 void DecodeTrace::draw_instant(const pv::data::decode::Annotation &a, QPainter &p,
503         int h, double x, int y) const
504 {
505         const QString text = a.annotations().empty() ?
506                 QString() : a.annotations().back();
507         const double w = min((double)p.boundingRect(QRectF(), 0, text).width(),
508                 0.0) + h;
509         const QRectF rect(x - w / 2, y - h / 2, w, h);
510
511         p.drawRoundedRect(rect, h / 2, h / 2);
512
513         p.setPen(Qt::black);
514         p.drawText(rect, Qt::AlignCenter | Qt::AlignVCenter, text);
515 }
516
517 void DecodeTrace::draw_range(const pv::data::decode::Annotation &a, QPainter &p,
518         int h, double start, double end, int y, const ViewItemPaintParams &pp,
519         int row_title_width) const
520 {
521         const double top = y + .5 - h / 2;
522         const double bottom = y + .5 + h / 2;
523         const vector<QString> annotations = a.annotations();
524
525         // If the two ends are within 1 pixel, draw a vertical line
526         if (start + 1.0 > end) {
527                 p.drawLine(QPointF(start, top), QPointF(start, bottom));
528                 return;
529         }
530
531         const double cap_width = min((end - start) / 4, EndCapWidth);
532
533         QPointF pts[] = {
534                 QPointF(start, y + .5f),
535                 QPointF(start + cap_width, top),
536                 QPointF(end - cap_width, top),
537                 QPointF(end, y + .5f),
538                 QPointF(end - cap_width, bottom),
539                 QPointF(start + cap_width, bottom)
540         };
541
542         p.drawConvexPolygon(pts, countof(pts));
543
544         if (annotations.empty())
545                 return;
546
547         const int ann_start = start + cap_width;
548         const int ann_end = end - cap_width;
549
550         const int real_start = max(ann_start, pp.left() + row_title_width);
551         const int real_end = min(ann_end, pp.right());
552         const int real_width = real_end - real_start;
553
554         QRectF rect(real_start, y - h / 2, real_width, h);
555         if (rect.width() <= 4)
556                 return;
557
558         p.setPen(Qt::black);
559
560         // Try to find an annotation that will fit
561         QString best_annotation;
562         int best_width = 0;
563
564         for (const QString &a : annotations) {
565                 const int w = p.boundingRect(QRectF(), 0, a).width();
566                 if (w <= rect.width() && w > best_width)
567                         best_annotation = a, best_width = w;
568         }
569
570         if (best_annotation.isEmpty())
571                 best_annotation = annotations.back();
572
573         // If not ellide the last in the list
574         p.drawText(rect, Qt::AlignCenter, p.fontMetrics().elidedText(
575                 best_annotation, Qt::ElideRight, rect.width()));
576 }
577
578 void DecodeTrace::draw_error(QPainter &p, const QString &message,
579         const ViewItemPaintParams &pp)
580 {
581         const int y = get_visual_y();
582
583         p.setPen(ErrorBgColour.darker());
584         p.setBrush(ErrorBgColour);
585
586         const QRectF bounding_rect =
587                 QRectF(pp.left(), INT_MIN / 2 + y, pp.right(), INT_MAX);
588         const QRectF text_rect = p.boundingRect(bounding_rect,
589                 Qt::AlignCenter, message);
590         const float r = text_rect.height() / 4;
591
592         p.drawRoundedRect(text_rect.adjusted(-r, -r, r, r), r, r,
593                 Qt::AbsoluteSize);
594
595         p.setPen(Qt::black);
596         p.drawText(text_rect, message);
597 }
598
599 void DecodeTrace::draw_unresolved_period(QPainter &p, int h, int left, int right) const
600 {
601         using namespace pv::data;
602         using pv::data::decode::Decoder;
603
604         double samples_per_pixel, pixels_offset;
605
606         const int64_t sample_count = decode_signal_->get_working_sample_count();
607         if (sample_count == 0)
608                 return;
609
610         const int64_t samples_decoded = decode_signal_->get_decoded_sample_count();
611         if (sample_count == samples_decoded)
612                 return;
613
614         const int y = get_visual_y();
615
616         tie(pixels_offset, samples_per_pixel) = get_pixels_offset_samples_per_pixel();
617
618         const double start = max(samples_decoded /
619                 samples_per_pixel - pixels_offset, left - 1.0);
620         const double end = min(sample_count / samples_per_pixel -
621                 pixels_offset, right + 1.0);
622         const QRectF no_decode_rect(start, y - (h / 2) + 0.5, end - start, h);
623
624         p.setPen(QPen(Qt::NoPen));
625         p.setBrush(Qt::white);
626         p.drawRect(no_decode_rect);
627
628         p.setPen(NoDecodeColour);
629         p.setBrush(QBrush(NoDecodeColour, Qt::Dense6Pattern));
630         p.drawRect(no_decode_rect);
631 }
632
633 pair<double, double> DecodeTrace::get_pixels_offset_samples_per_pixel() const
634 {
635         assert(owner_);
636
637         const View *view = owner_->view();
638         assert(view);
639
640         const double scale = view->scale();
641         assert(scale > 0);
642
643         const double pixels_offset =
644                 ((view->offset() - decode_signal_->start_time()) / scale).convert_to<double>();
645
646         double samplerate = decode_signal_->samplerate();
647
648         // Show sample rate as 1Hz when it is unknown
649         if (samplerate == 0.0)
650                 samplerate = 1.0;
651
652         return make_pair(pixels_offset, samplerate * scale);
653 }
654
655 pair<uint64_t, uint64_t> DecodeTrace::get_sample_range(
656         int x_start, int x_end) const
657 {
658         double samples_per_pixel, pixels_offset;
659         tie(pixels_offset, samples_per_pixel) =
660                 get_pixels_offset_samples_per_pixel();
661
662         const uint64_t start = (uint64_t)max(
663                 (x_start + pixels_offset) * samples_per_pixel, 0.0);
664         const uint64_t end = (uint64_t)max(
665                 (x_end + pixels_offset) * samples_per_pixel, 0.0);
666
667         return make_pair(start, end);
668 }
669
670 int DecodeTrace::get_row_at_point(const QPoint &point)
671 {
672         if (!row_height_)
673                 return -1;
674
675         const int y = (point.y() - get_visual_y() + row_height_ / 2);
676
677         /* Integer divison of (x-1)/x would yield 0, so we check for this. */
678         if (y < 0)
679                 return -1;
680
681         const int row = y / row_height_;
682
683         if (row >= (int)visible_rows_.size())
684                 return -1;
685
686         return row;
687 }
688
689 const QString DecodeTrace::get_annotation_at_point(const QPoint &point)
690 {
691         using namespace pv::data::decode;
692
693         if (!enabled())
694                 return QString();
695
696         const pair<uint64_t, uint64_t> sample_range =
697                 get_sample_range(point.x(), point.x() + 1);
698         const int row = get_row_at_point(point);
699         if (row < 0)
700                 return QString();
701
702         vector<pv::data::decode::Annotation> annotations;
703
704         decode_signal_->get_annotation_subset(annotations, visible_rows_[row],
705                 sample_range.first, sample_range.second);
706
707         return (annotations.empty()) ?
708                 QString() : annotations[0].annotations().front();
709 }
710
711 void DecodeTrace::hover_point_changed()
712 {
713         assert(owner_);
714
715         const View *const view = owner_->view();
716         assert(view);
717
718         QPoint hp = view->hover_point();
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         hp.setX(hp.x() - (text_size.width() / 2) - padding);
748
749         hp.setY(get_visual_y() - (row_height_ / 2) +
750                 (hover_row * row_height_) -
751                 row_height_ - text_size.height() - padding);
752
753         QToolTip::showText(view->viewport()->mapToGlobal(hp), ann);
754 }
755
756 void DecodeTrace::create_decoder_form(int index,
757         shared_ptr<data::decode::Decoder> &dec, QWidget *parent,
758         QFormLayout *form)
759 {
760         GlobalSettings settings;
761
762         assert(dec);
763         const srd_decoder *const decoder = dec->decoder();
764         assert(decoder);
765
766         const bool decoder_deletable = index > 0;
767
768         pv::widgets::DecoderGroupBox *const group =
769                 new pv::widgets::DecoderGroupBox(
770                         QString::fromUtf8(decoder->name),
771                         tr("%1:\n%2").arg(QString::fromUtf8(decoder->longname),
772                                 QString::fromUtf8(decoder->desc)),
773                         nullptr, decoder_deletable);
774         group->set_decoder_visible(dec->shown());
775
776         if (decoder_deletable) {
777                 delete_mapper_.setMapping(group, index);
778                 connect(group, SIGNAL(delete_decoder()), &delete_mapper_, SLOT(map()));
779         }
780
781         show_hide_mapper_.setMapping(group, index);
782         connect(group, SIGNAL(show_hide_decoder()),
783                 &show_hide_mapper_, SLOT(map()));
784
785         QFormLayout *const decoder_form = new QFormLayout;
786         group->add_layout(decoder_form);
787
788         const vector<DecodeChannel> channels = decode_signal_->get_channels();
789
790         // Add the channels
791         for (DecodeChannel ch : channels) {
792                 // Ignore channels not part of the decoder we create the form for
793                 if (ch.decoder_ != dec)
794                         continue;
795
796                 QComboBox *const combo = create_channel_selector(parent, &ch);
797                 QComboBox *const combo_init_state = create_channel_selector_init_state(parent, &ch);
798
799                 channel_id_map_[combo] = ch.id;
800                 init_state_map_[combo_init_state] = ch.id;
801
802                 connect(combo, SIGNAL(currentIndexChanged(int)),
803                         this, SLOT(on_channel_selected(int)));
804                 connect(combo_init_state, SIGNAL(currentIndexChanged(int)),
805                         this, SLOT(on_init_state_changed(int)));
806
807                 QHBoxLayout *const hlayout = new QHBoxLayout;
808                 hlayout->addWidget(combo);
809                 hlayout->addWidget(combo_init_state);
810
811                 if (!settings.value(GlobalSettings::Key_Dec_InitialStateConfigurable).toBool())
812                         combo_init_state->hide();
813
814                 const QString required_flag = ch.is_optional ? QString() : QString("*");
815                 decoder_form->addRow(tr("<b>%1</b> (%2) %3")
816                         .arg(ch.name, ch.desc, required_flag), hlayout);
817         }
818
819         // Add the options
820         shared_ptr<binding::Decoder> binding(
821                 new binding::Decoder(decode_signal_, dec));
822         binding->add_properties_to_form(decoder_form, true);
823
824         bindings_.push_back(binding);
825
826         form->addRow(group);
827         decoder_forms_.push_back(group);
828 }
829
830 QComboBox* DecodeTrace::create_channel_selector(QWidget *parent, const DecodeChannel *ch)
831 {
832         const auto sigs(session_.signalbases());
833
834         // Sort signals in natural order
835         vector< shared_ptr<data::SignalBase> > sig_list(sigs.begin(), sigs.end());
836         sort(sig_list.begin(), sig_list.end(),
837                 [](const shared_ptr<data::SignalBase> &a,
838                 const shared_ptr<data::SignalBase> &b) {
839                         return strnatcasecmp(a->name().toStdString(),
840                                 b->name().toStdString()) < 0; });
841
842         QComboBox *selector = new QComboBox(parent);
843
844         selector->addItem("-", qVariantFromValue((void*)nullptr));
845
846         if (!ch->assigned_signal)
847                 selector->setCurrentIndex(0);
848
849         for (const shared_ptr<data::SignalBase> &b : sig_list) {
850                 assert(b);
851                 if (b->logic_data() && b->enabled()) {
852                         selector->addItem(b->name(),
853                                 qVariantFromValue((void*)b.get()));
854
855                         if (ch->assigned_signal == b.get())
856                                 selector->setCurrentIndex(selector->count() - 1);
857                 }
858         }
859
860         return selector;
861 }
862
863 QComboBox* DecodeTrace::create_channel_selector_init_state(QWidget *parent,
864         const DecodeChannel *ch)
865 {
866         QComboBox *selector = new QComboBox(parent);
867
868         selector->addItem("0", qVariantFromValue((int)SRD_INITIAL_PIN_LOW));
869         selector->addItem("1", qVariantFromValue((int)SRD_INITIAL_PIN_HIGH));
870         selector->addItem("X", qVariantFromValue((int)SRD_INITIAL_PIN_SAME_AS_SAMPLE0));
871
872         selector->setCurrentIndex(ch->initial_pin_state);
873
874         selector->setToolTip("Initial (assumed) pin value before the first sample");
875
876         return selector;
877 }
878
879 void DecodeTrace::on_new_annotations()
880 {
881         if (!delayed_trace_updater_.isActive())
882                 delayed_trace_updater_.start();
883 }
884
885 void DecodeTrace::on_delayed_trace_update()
886 {
887         if (owner_)
888                 owner_->row_item_appearance_changed(false, true);
889 }
890
891 void DecodeTrace::on_decode_finished()
892 {
893         if (owner_)
894                 owner_->row_item_appearance_changed(false, true);
895 }
896
897 void DecodeTrace::delete_pressed()
898 {
899         on_delete();
900 }
901
902 void DecodeTrace::on_delete()
903 {
904         session_.remove_decode_signal(decode_signal_);
905 }
906
907 void DecodeTrace::on_channel_selected(int)
908 {
909         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
910
911         // Determine signal that was selected
912         const data::SignalBase *signal =
913                 (data::SignalBase*)cb->itemData(cb->currentIndex()).value<void*>();
914
915         // Determine decode channel ID this combo box is the channel selector for
916         const uint16_t id = channel_id_map_.at(cb);
917
918         decode_signal_->assign_signal(id, signal);
919 }
920
921 void DecodeTrace::on_channels_updated()
922 {
923         if (owner_)
924                 owner_->row_item_appearance_changed(false, true);
925 }
926
927 void DecodeTrace::on_init_state_changed(int)
928 {
929         QComboBox *cb = qobject_cast<QComboBox*>(QObject::sender());
930
931         // Determine inital pin state that was selected
932         int init_state = cb->itemData(cb->currentIndex()).value<int>();
933
934         // Determine decode channel ID this combo box is the channel selector for
935         const uint16_t id = init_state_map_.at(cb);
936
937         decode_signal_->set_initial_pin_state(id, init_state);
938 }
939
940 void DecodeTrace::on_stack_decoder(srd_decoder *decoder)
941 {
942         decode_signal_->stack_decoder(decoder);
943
944         create_popup_form();
945 }
946
947 void DecodeTrace::on_delete_decoder(int index)
948 {
949         decode_signal_->remove_decoder(index);
950
951         // Update the popup
952         create_popup_form();
953 }
954
955 void DecodeTrace::on_show_hide_decoder(int index)
956 {
957         const bool state = decode_signal_->toggle_decoder_visibility(index);
958
959         assert(index < (int)decoder_forms_.size());
960         decoder_forms_[index]->set_decoder_visible(state);
961
962         if (owner_)
963                 owner_->row_item_appearance_changed(false, true);
964 }
965
966 } // namespace trace
967 } // namespace views
968 } // namespace pv