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