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