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